У меня есть список неотсортированных строк, в котором есть одна из следующих записей: {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"));

Мне нужно отсортировать / (сгруппировать) их в произвольном порядке, принимая по одному элементу за раз, чтобы получить такой результат:

[A, B, C, D, A, B, C, D, A, B, C, D, A, D]

Я изо всех сил пытаюсь придумать, как это сделать. Любая помощь?

Я попытался использовать пользовательский Comparator<String>, но не смог реализовать логику first A < second A и first D < second A.

Также пробовал Stream. groupingBy:

Collection<List<String>> coll = strings.stream().collect(Collectors.groupingBy(s -> s)).values();

Который группирует одинаковые строки в группы.

[[A, A, A, A], [B, B, B], [C, C, C], [D, D, D, D]]

Но я не уверен, как брать по одному элементу из приведенных выше списков, пока элементы не станут доступны. Есть ли у кого-нибудь подход к тому, как действовать здесь? Нужна подсказка в правильном направлении.

3
wannaBeDev 15 Окт 2021 в 16:29

4 ответа

Лучший ответ

Создание совершенно нового списка может привести к некоторым другим решениям, например:

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

С strings входным списком и ordered выходным.

3
sp00m 15 Окт 2021 в 14:18

Добавьте числовой префикс к каждому значению, отсортируйте и удалите префикс, с ограничением размер массива не может быть намного больше, чем числовой префикс

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());
2
ProGu 15 Окт 2021 в 13:52

Это примерно та же логика, что и ответ sp00m, но реализованная с двумя потоками:

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

Сортировкой занимается TreeMap. Просто убедитесь, что ваши фактические элементы списка сопоставимы (или что вы предоставили правильного поставщика TreeMap)

2
ernest_k 15 Окт 2021 в 14:37

Ну вот и другой подход.

Во-первых, мы могли бы получить список со всеми элементами, как вы описываете в своем вопросе.

[
    [A, A, A, A],
    [B, B, B],
    [C, C, C],
    [D, D, D, D]
]
Collection<List<String>> chunks = strs.stream()
    .collect(Collectors.groupingBy(Function.identity(), TreeMap::new, Collectors.toList()))
    .values();

Вы можете вставить собственный Comparator, заменив TreeMap::new на () -> new TreeMap<>(comparator).

Тогда мы могли бы просто использовать это, чтобы получить все группы ABCD.

IntStream.iterate(0, i -> i + 1)
    .mapToObj(i -> chunks.stream()
        .map(sublist -> i < sublist.size() ? sublist.get(i) : null)
        .filter(Objects::nonNull)
        .toList())
    .takeWhile(list -> !list.isEmpty())
    .forEach(System.out::println);
0
MC Emperor 15 Окт 2021 в 14:58