如何将列表排序为自定义顺序,形成不同的组

How to sort a list into a custom order forming kind of distinct groups

我有一个未排序的字符串列表,其中条目是 {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 Afirst 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]]

但我不确定如何一次从上面的列表中取出一个元素,直到没有可用的元素为止。有没有人对如何在这里进行任何方法?需要正确方向的提示。

为每个值添加一个数字前缀,排序和删除前缀,限制数组大小不能远大于数字前缀

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

构建一个全新的列表可能会导致一些其他的解决方案,例如:

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 是输出列表。

这与的逻辑大致相同,但使用两个流实现:

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 供应商)

好吧,这是另一种方法。

首先,我们可以获得包含所有项目的列表,如您在问题中所述。

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

您可以通过将 TreeMap::new 替换为 () -> new TreeMap<>(comparator) 来插入自定义 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);

这里发生的事情是,我们遍历每个子列表并获取第一个元素,然后获取每个第二个元素,等等。


如果将获取一堆列表的特定索引的代码放入单独的方法中,这将变得更好可读:

public static <T> Stream<T> nthElement(Collection<? extends List<T>> list, int index) {
    return list.stream()
        .map(sublist -> index < sublist.size() ? sublist.get(index) : null)
        .filter(Objects::nonNull);
}
IntStream.iterate(0, i -> i + 1)
    .mapToObj(i -> nthElement(chunks, i).toList())
    .takeWhile(list -> !list.isEmpty())
    .forEach(System.out::println);

不那么优雅,但更清楚发生了什么。您可以在每个字符串前放置一个整数,表示遇到该字符串值的次数。然后正常排序并使用正则表达式替换整数值。

public static void main(String[] args) {
    List<String> strings = new ArrayList<>(Arrays.asList("A","C","B","D","D","A","B","C","A","D","B","D","A","C"));
    List<String> sortableString = stringTransform(strings);
    sortableString.stream().sorted().forEach(s -> System.err.print(s.replaceAll("[0-9]", "")));
}


private static List<String> stringTransform(List<String> stringList) {
    Map<String, Integer> stringMap = new HashMap<>();
    List<String> result = new ArrayList<>();
    for (String string : stringList) {
        Integer integer = stringMap.get(string);
        if (integer == null) {
            integer = 0;
        } else {
            integer++;
        }
        stringMap.put(string, integer);
        result.add(integer + string);
    }
    return result;
}