分组为int数组列表

时间:2018-03-12 11:37:35

标签: java java-8 java-stream

我有一个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

我该怎么办?

4 个答案:

答案 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上花费了所有精力并且必须编写自定义解决方案时,这非常强大。