如何将列表排序为自定义顺序,形成不同的组
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 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]]
但我不确定如何一次从上面的列表中取出一个元素,直到没有可用的元素为止。有没有人对如何在这里进行任何方法?需要正确方向的提示。
为每个值添加一个数字前缀,排序和删除前缀,限制数组大小不能远大于数字前缀
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;
}
我有一个未排序的字符串列表,其中条目是 {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]]
但我不确定如何一次从上面的列表中取出一个元素,直到没有可用的元素为止。有没有人对如何在这里进行任何方法?需要正确方向的提示。
为每个值添加一个数字前缀,排序和删除前缀,限制数组大小不能远大于数字前缀
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;
}