I have a list of unsorted strings, where entries are one of {A,B,C,D}:
List<String> strings = new ArrayList<>(Arrays.asList("A","C","B","D","D","A","B","C","A","D","B","D","A","C"));
I need to sort / (group) them in a custom order taking one item at time to have a result like:
[A, B, C, D, A, B, C, D, A, B, C, D, A, D]
I am struggling to come up with an idea how to do so. Any help?
I have tried to use a custom Comparator<String> but not able to implement the logic that first A < second A and first D < second A.
Also tried Stream. groupingBy:
Collection<List<String>> coll = strings.stream().collect(Collectors.groupingBy(s -> s)).values();
which groups same strings into groups.
[[A, A, A, A], [B, B, B], [C, C, C], [D, D, D, D]]
But I am not sure how to take one element at a time from above lists till no elements are available. Does anyone have any approach on how to proceed here? Need a hint in the right direction.
Building a whole new list could lead to some other solutions, for example:
Map<String, Long> counts = strings.stream().collect(groupingBy(identity(), TreeMap::new, counting()));
List<String> ordered = new ArrayList<>();
while (!counts.isEmpty()) {
for (Iterator<Map.Entry<String, Long>> it = counts.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Long> entry = it.next();
ordered.add(entry.getKey());
long newCount = entry.getValue() - 1;
if (newCount == 0) {
it.remove();
} else {
entry.setValue(newCount);
}
}
}
With strings being the input list and ordered the output.
Add a number prefix to each value, sort and remove the prefix, with limitation the array size cannot be far bigger than the number prefix
List<String> strings = new ArrayList<>(Arrays.asList("A","C","B","D","D","A","B","C","A","D","B","D","A","C"));
Map<String, Integer> m = new HashMap<>();
strings.stream()
.map(i -> String.format("%dx%s", (100000 + m.merge(i, 1, (n, w) -> n+w)), i))
.sorted()
.map(i -> i.replaceFirst("^\\d+x", ""))
.collect(Collectors.toList());
This is roughly the same logic as sp00m's answer, but implemented with two streams:
Map<String, Long> groups = strings.stream()
.collect(Collectors.groupingBy(Function.identity(),
TreeMap::new,
Collectors.counting()));
List<String> result = IntStream.range(0, groups.values().stream()
.mapToInt(Long::intValue).max().orElseThrow())
.mapToObj(c -> groups.keySet().stream().filter(k -> groups.get(k) > c))
.flatMap(Function.identity())
.collect(Collectors.toList());
The sorting is taken care of by TreeMap. Just be sure that your actual list elements are comparable (or that you give the right TreeMap supplier)