我有一个int数组列表。我想通过唯一的数组进行分组。
int[] array1 = new int[]{1, 2, 3};
int[] array2 = new int[]{1, 2, 3}; //array1 = array2
int[] array3 = new int[]{0, 2, 3};
List<int[]> test = new ArrayList<>();
test.add(array1);
test.add(array2);
test.add(array3);
test.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
不幸的是,它不起作用。它分组就好像任何数组都是唯一的一样:
{1, 2, 3} - 1
{1, 2, 3} - 1
{0, 2, 3} - 1
我期待:
{1, 2, 3} - 2
{0, 2, 3} - 1
我该怎么办?
答案 0 :(得分:4)
它分组就像任何数组都是唯一的一样:
情况确实如此。
无论如何,实际上你都会遇到一些困难:内置Collector
,例如groupingBy()
和toMap()
或循环,因为两个具有相同内容的数组不等于equals()
(以及hashCode()
)
您应该考虑将List<Integer>
用于此用例,而不是int[]
。
例如:
public static void main(String[] args) {
int[] array1 = new int[] { 1, 2, 3 };
int[] array2 = new int[] { 1, 2, 3 }; // array1 = array2
int[] array3 = new int[] { 0, 2, 3 };
List<List<Integer>> test = new ArrayList<>();
test.add(Arrays.stream(array1)
.boxed()
.collect(Collectors.toList()));
test.add(Arrays.stream(array2)
.boxed()
.collect(Collectors.toList()));
test.add(Arrays.stream(array3)
.boxed()
.collect(Collectors.toList()));
Map<List<Integer>, Long> map = test.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
System.out.println(map);
}
输出:
{[0,2,3] = 1,[1,2,3] = 2}
答案 1 :(得分:1)
尝试以下方法:
Map<Integer, Long> res = test.stream().collect(Collectors.groupingBy(Arrays::hashCode, Collectors.counting()));
请注意,在地图而不是实际数组中,您将拥有数组哈希码。 如果你想将实际数组作为键 - 你应该将它包装在类中,并使用基于数组内容实现的equals / hashcode。
答案 2 :(得分:1)
您可以使用列表和java8流。
Map<List<Integer>, Long> mapList = Stream.of(array1, array2, array3)
.map(Arrays::stream)
.map(IntStream::boxed)
.map(stream -> stream.collect(Collectors.toList()))
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
如果您想要Map<int[], Long>
,可以在上面collect()
之后继续。
// ... collect ...
.entrySet().stream().collect(Collectors.toMap(entry -> entry.getKey()
.stream().mapToInt(i -> i).toArray(), Entry::getValue));
// returns Map<int[], Long>
我仍然认为这个问题强调使用数组。您可以为int[]
对象创建包装类。
这只是int数组包装器的一个例子,即使可以使用更复杂的类,例如工厂模式允许使用所有数组基元甚至数组对象。
public class IntArray {
private final int[] array;
private IntArray(final int[] array) {
this.array = array;
}
public static IntArray wrap(final int[] array) {
return new IntArray(array);
}
public int[] unwrap() {
return array;
}
@Override
public boolean equals(final Object obj) {
return obj instanceof IntArray
&& Arrays.equals(((IntArray) obj).array, array);
}
@Override
public int hashCode() {
return Arrays.hashCode(array);
}
@Override
public String toString() {
return Arrays.toString(array);
}
}
此处wrap(..)
方法是可选的,IntArray::new
可以替代使用,toString()
方法也是可选的,以允许将内部数组转换为字符串而无需解包。
必要的方法是equals(..)
和hashcode()
,因为它们是地图正常运作所必需的。
以下是有关它的更多信息。
Understanding the workings of equals and hashCode in a HashMap
最后它可以像。
一样使用Map<IntArray, Long> mapArray = Stream.of(array1, array2, array3)
.map(IntArray::wrap)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
如果你想将数组作为地图的关键字(作为Map<int[], Long>
),你可以再做一遍(请注意,在这种情况下,不创建新数组,它只使用第一个唯一数组它发现了。)
// ... collect ...
.entrySet().stream().collect(Collectors.toMap(entry -> entry.getKey()
.unwrap(), Entry::getValue));
// returns Map<int[], Long>
答案 3 :(得分:0)
如果您考虑将int arrays
转换为列表,则可以在collect
中引入自定义逻辑,如下所示:
test
.parallelStream()
.map(array -> Arrays.stream(array)
.boxed()
.collect(Collectors.toList())
)
.collect(HashMap::new,
(HashMap<List<Integer>, Integer> map, List<Integer> list) -> {
if (map.containsKey(list)) {
map.put(list, map.get(list) + 1);
} else {
map.put(list, 1);
}
},
(HashMap<List<Integer>, Integer> map1, HashMap<List<Integer>, Integer> map2) -> {
map2.entrySet().forEach(entry -> {
if (map1.containsKey(entry.getKey())) {
map1.put(entry.getKey(), map1.get(entry.getKey()) + 1);
} else {
map1.put(entry.getKey(), 1);
}
});
}
)
要了解这里发生的事情,请参阅下面的收集方法的定义:
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner)
下面,
Supplier
基本上是返回的类型。
accumulator
是累加消费者,请注意我是如何累积结果的。
combiner
仅在您执行并行执行时才有效。因此,我结合了并行执行的结果,使得输出保持相同。您可以简单地认为我们在这里使用了Divide and Conquer
。
当您在可用的Collectors
上花费了所有精力并且必须编写自定义解决方案时,这非常强大。