存储重复字符串元素的最优雅方式,在每个重复元素的末尾增加计数器

问题描述

我想将重复项 String[] 转换为 List<String>,其中每个下一个重复的元素都会在其名称增加计数器。

示例:

Test,TestA,TestB,Test,TestB

应转换为:

Test,Test1,TestA1,TestA2,TestB1

结果必须与输入数据具有相同的顺序,仅添加计数器。

目前我写了这样的东西,但我不认为这是一种优雅有效的方法,我正在寻找更合适的方法

String[] header = new String[]{"Test","TestA","TestB","Test","TestB"};
List<String> newHeader = new ArrayList<>();
for (String column : header) {
    if (!newHeader.contains(column)) {
        newHeader.add(column);
    } else {
        int counter = 1;
        String columnIterName = column + counter;
        while (newHeader.contains(columnIterName)) {
            columnIterName = column + counter;
            counter++;
        }
        newHeader.add(columnIterName);
    }
}
return newHeader;

解决方法

假设您只关心原始列表中的重复项:

如果您使用 Map 来跟踪重复项,您可以将代码简化为:

   public static List<String> some_method(){
        String[] header = new String[]{"Test","TestA","TestB","Test","TestB"};
        Map<String,Integer> track = new HashMap<>();
        List<String> newHeader = new ArrayList<>();
        for(String s : header){
            Integer count = track.get(s);
            if(count != null){
                count++;
                track.put(s,count);
                newHeader.add(s + count);
            }
            else{
                track.put(s,0);
                newHeader.add(s);
            }
        }
        return newHeader;
    }

这样你也可以避免内部的 while 循环。

如果您还关心正在构建的列表中的重复项,例如对于输入:

Test Test Test1

你期望的输出:

Test Test1 Test11

然后您可以使用以下内容:

public static List<String> some_method(){
    String[] header = new String[]{"Test","Test1"};
    Map<String,Integer> track = new HashMap<>();
    List<String> newHeader = new ArrayList<>();
    for(String s : header){
        Integer count = track.get(s);
        if(count != null){
            count++;
            track.put(s,count);
            newHeader.add(s + count);
            track.put(s + count,0);
        }
        else{
            track.put(s,0);
            newHeader.add(s);
        }
    }
    return newHeader;
}
,

从 Java 8 开始,Map 具有允许在缺少键时计算值的方法,例如,Map::compute 允许使用 BiFunction 增加计数器:

更新:处理特定测试:"Test","Test1" 当由于附加计数器而创建重复项时,应使用另一种方法 putIfAbsent

static List<String> convert(String ... header) {
    Map<String,Integer> counters = new HashMap<>();
    List<String> result = new ArrayList<>();
    for (String column : header) {
        int count = counters.compute(column,(k,v) -> v == null ? 0 : v + 1);
        String toAdd = column + (count == 0 ? "" : Integer.toString(count));
        counters.putIfAbsent(toAdd,0);
        result.add(toAdd);
    }
    return result;
}

使用 Stream API 的类似解决方案可能如下所示。

更新:此处以“作弊模式”调用 putIfAbsent(使用 peek

static List<String> convertStream(String ... header) {
    Map<String,Integer> counters = new HashMap<>();
    
    return Arrays.stream(header)
        .map(column -> column + 
            (counters.compute(column,v) -> v == null ? 0 : v + 1) > 0 
            ? Integer.toString(counters.get(column)) : "")
        )
        .peek(column -> counters.putIfAbsent(column,0))
        .collect(Collectors.toList());
}

测试:

String[][] tests = {
    {"Test","Test1"},{"Test","Test1","TestB"}
};
        
for (String[] test : tests) {
    System.out.println("convert: " + convert(test));
    System.out.println("stream : " + convertStream(test));

    System.out.println("dreamcr: " + some_method(test));
    System.out.println("------------\n");
}

输出:

convert: [Test,Test1,Test11]
stream : [Test,Test11]
dreamcr: [Test,Test11]
------------

convert: [Test,TestA,TestB,Test11,TestA1,TestA2,TestB1]
stream : [Test,TestB1]
dreamcr: [Test,TestB1]
------------
,

ArrayList 似乎不是匹配字符串的最佳选择,因为搜索 (.contains) 效率不高。依次尝试所有后缀是一种很大的浪费。我想 Dictionary 会更好。

那么逻辑是

  • 扫描列表

    • 如果字符串不在字典中,则以count 1输入

    • else 增加其计数并将其附加到字符串(在原始列表中就地)。

,

您可以简化为以下内容:

public class Counter {
    
    public static List<String> someMethod(String[] header){
        Map<String,Integer> track = new HashMap<>();
        List<String> newHeader = new ArrayList<>();
        for(String s : header){
            int count = track.compute(s,v) -> (v == null) ? 0 : v + 1);
            if(count != 0)
                track.put(s + count,0);
            newHeader.add(count == 0 ? s : s + count);
        }
        return newHeader;
    }

    public static void main(String[] args) {
        someMethod(new String[]{"Test","Test1"}).forEach(System.out::println);
    }
}