Dawid
Dawid

Reputation: 348

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

I want to transform duplicates String[] to List<String> where every next duplicated element will have increasing counter after it name.

Example:

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

should be transformed to:

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

Result must have the same order like input data, only counter is added.

Currently I wrote something like this but I don't think if it elegant and efficient way to make this and I'm looking for more proper way:

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;

Upvotes: 1

Views: 466

Answers (4)

dreamcrash
dreamcrash

Reputation: 51643

You can simplified to the following :

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);
    }
}

Upvotes: 0

Nowhere Man
Nowhere Man

Reputation: 19575

Since Java 8, Map has methods which allow to compute values if a key is missing, for example, Map::compute allows to increment the counter using a BiFunction:

Update: to handle specific test: "Test", "Test", "Test1" when a duplicate is created as a result of appending the counter, another method putIfAbsent should be used.

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;
}

Similar solution using Stream API may look as follows.

Update: here putIfAbsent is invoked in "cheat mode" (using 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());
}

Tests:

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");
}

Output:

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]
------------

Upvotes: 1

user1196549
user1196549

Reputation:

An ArrayList does not seem to be the best choice to match the strings, as the searches (.contains) are not efficient. An trying all suffixes in turn is a big waste. A Dictionary would be better I guess.

Then the logics is

  • scan the list

    • if the string is not in the dictionary, enter it with count 1

    • else increment its count and append it to the string (in-place in the original list).

Upvotes: 0

dreamcrash
dreamcrash

Reputation: 51643

Assuming that you only care about the duplicates in the original list then:

If you use a Map to keep track of the duplicates you can simplified your code to:

   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;
    }

With this you also avoid the inner while loop.

If you also care about the duplicates on the list being build, for instance for the input:

Test Test Test1

you expect the output:

Test Test1 Test11

then you can use the following:

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;
}

Upvotes: 3

Related Questions