在每个重复元素的末尾增加计数器来存储重复的 String 元素的最优雅的方式

Most elegant way to store duplicate String elements with increasing counter at the end of every duplicated element

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

示例:

Test, TestA, TestB, Test, TestA, TestA, TestB

应转换为:

Test, TestA, TestB, Test1, TestA1, TestA2, TestB1

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

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

String[] header = new String[]{"Test", "TestA", "TestB", "Test", "TestA", "TestA", "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;

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

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

   public static List<String> some_method(){
        String[] header = new String[]{"Test", "TestA", "TestB", "Test", "TestA", "TestA", "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","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;
}

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

那么逻辑就是

  • 扫一扫列表

    • 如果该字符串不在字典中,则输入第 1 个

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

由于 Java 8,Map 具有允许在缺少键的情况下计算值的方法,例如,Map::compute 允许使用 BiFunction:

Update:处理特定测试:"Test", "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, (k, v) -> v == null ? 0 : v + 1) > 0 
            ? Integer.toString(counters.get(column)) : "")
        )
        .peek(column -> counters.putIfAbsent(column, 0))
        .collect(Collectors.toList());
}

测试:

String[][] tests = {
    {"Test", "Test", "Test1"},
    {"Test", "Test1", "TestA", "TestB", "Test", "Test1", "TestA", "TestA", "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, Test1, Test11]
dreamcr: [Test, Test1, Test11]
------------

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

您可以简化为以下内容:

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, (k, 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","Test","Test1"}).forEach(System.out::println);
    }
}