简体   繁体   English

ArrayLists笛卡尔积元素的正确顺序

[英]Correct order of elements of Cartesian product of ArrayLists

I tried to generate Cartesian product of unknown number of ArrayLists (of fixed type) based on this answer: Cartesian product of an arbitrary number of sets .我试图根据这个答案生成未知数量的 ArrayLists(固定类型)的笛卡尔积:任意数量的集合的笛卡尔积 But I have found something strange.但是我发现了一些奇怪的东西。 The cartesian products is always given in reverse order.笛卡尔积总是以相反的顺序给出。 For example, if A and B are two Lists, B's elements are given first and A's elements are given second in cartesian pair.例如,如果 A 和 B 是两个 List,则在笛卡尔对中,首先给出 B 的元素,然后给出 A 的元素。 What could be possible reason?可能的原因是什么? How to fix that?如何解决? Original answerer says, ordering does not matter in Cartesian product.原始回答者说,在笛卡尔积中订购无关紧要。 But I think ordering is the main thing while making cartesian products especially when each set represents coordinates of plane.但我认为在制作笛卡尔产品时订购是主要的事情,尤其是当每组代表平面坐标时。

Modified Code:修改代码:

private static Set<ArrayList<Double>> cartesianProduct(ArrayList<ArrayList<Double>> sets) {
    if (sets.size() < 2)
        throw new IllegalArgumentException(
                "Can't have a product of fewer than two sets (got " +
                        sets.size() + ")");

    return _cartesianProduct(0, sets);
}

private static Set<ArrayList<Double>> _cartesianProduct(int index, ArrayList<ArrayList<Double>> sets) {
    Set<ArrayList<Double>> ret = new HashSet<>();
    if (index == sets.size()) {
        ret.add(new ArrayList<>());
    } else {
        for (Double obj : sets.get(index)) {
            for (ArrayList<Double> set : _cartesianProduct(index + 1, sets)) {
                set.add(obj);
                ret.add(set);
            }
        }
    }
    return ret;
}

Output: Output:

ArrayList<Double> l1 = new ArrayList<>(Arrays.asList(1.0, 2.0));
ArrayList<Double> l2 = new ArrayList<>(Arrays.asList(4.0, 5.0));
ArrayList<ArrayList<Double>> l = new ArrayList<>(Arrays.asList(l1, l2));
Set<ArrayList<Double>> a = cartesianProduct(l);

// a = [[4.0, 1.0], [4.0, 2.0], [5.0, 1.0], [5.0, 2.0]]

This happens because of recursion.这是因为递归。 Index is initially 0, so at the line for (ArrayList<Double> set: _cartesianProduct(index + 1, sets)) { , your code calls cartesianProduct again with index=1.索引最初为 0,因此在for (ArrayList<Double> set: _cartesianProduct(index + 1, sets)) {行,您的代码再次使用 index=1 调用cartesianProduct Again it reaches that line, and calls cartesianProduct with index=2.它再次到达那条线,并使用 index=2 调用cartesianProduct产品。 When it is at index=2, it reaches its base case and returns a set with an empty ArrayList .当它位于 index=2 时,它达到其基本情况并返回一个带有空ArrayList的集合。

Then it goes back to the stackframe where index=1 (remember, obj is 4.0 because sets.get(1) is the ArrayList containing 4 and 6).然后它返回到 index=1 的堆栈帧(请记住, obj是 4.0,因为sets.get(1)是包含 4 和 6 的 ArrayList)。 It adds all the doubles in sets.get(index) (here it is 4.0 and 6.0) to their own ArrayLists in ret .它将sets.get(index)中的所有双精度数(这里是 4.0 和 6.0)添加到ret中它们自己的 ArrayLists 中。 Then it reaches the end of the foreach loop and returns the set, which now has 2 ArrayLists, one containing a 4.0 and the other 6.0.然后它到达 foreach 循环的末尾并返回集合,该集合现在有 2 个 ArrayList,一个包含 4.0,另一个包含 6.0。

The same happens at index=0, so the first list(or set)'s elements are added after the second list's elements.在 index=0 时也是如此,因此第一个列表(或集合)的元素添加第二个列表的元素之后。 That's why you get reverse results.这就是为什么你会得到相反的结果。

To fix this, you could decrement index every time, going from sets.size() to 0 instead of the other way around.要解决此问题,您可以每次减少索引,从 sets.size() 变为 0 而不是相反。 To reverse it, you can also simply call Collections.reverse() on every set inside the result.要反转它,您也可以简单地在结果中的每个集合上调用 Collections.reverse() 。

//Fix by decrementing index
private static Set<ArrayList<Double>> cartesianProduct(ArrayList<ArrayList<Double>> sets) {
    if (sets.size() < 2)
        throw new IllegalArgumentException(
                "Can't have a product of fewer than two sets (got " + sets.size() + ")");
    //Be sure to start at the end of 'sets' so you can go down by one
    return cartesianProduct(sets.size() - 1, sets);
}

private static Set<ArrayList<Double>> cartesianProduct(int index, ArrayList<ArrayList<Double>> sets) {
    Set<ArrayList<Double>> ret = new HashSet<>();
    //Counting to 0 instead of to the end of the sets ArrayList
    if (index < 0) {
        ret.add(new ArrayList<>());
    } else {
        for (Double obj : sets.get(index)) {
            for (ArrayList<Double> set : cartesianProduct(index - 1, sets)) {
                set.add(obj);
                ret.add(set);
            }
        }
    }
    return ret;
}
//Alternative answer using Collections.reverse
private static Set<ArrayList<Double>> cartesianProduct(ArrayList<ArrayList<Double>> sets) {
    if (sets.size() < 2)
        throw new IllegalArgumentException(
                "Can't have a product of fewer than two sets (got " + sets.size() + ")");
    //This basically goes through the set of sets and reverses each ArrayList
    return cartesianProduct(0, sets).stream().map(Collections::reverse).collect(Collectors.toSet());
}

This method constructs the cartesian product in reverse because it creates the product "inside out" - when it's returning out of the recursion.此方法反向构造笛卡尔积,因为它“由内而外”创建积 - 当它从递归中返回时。

Printed out the value returned by each level in the recursion and you'll see how it happens.打印出递归中每个级别返回的值,您将看到它是如何发生的。

The second level of recursion works on list B and returns [[4], [5]].第二级递归作用于列表 B 并返回 [[4], [5]]。

The first level of recursion takes [[4], [5]] and uses the list.add method to add items from the list A. This method adds items to the end of the list so the result is [[4, 1], [5, 1], [4, 2], [5, 2]].第一级递归采用 [[4], [5]] 并使用list.add方法从列表 A 中添加项目。该方法将项目添加到列表的末尾,因此结果为 [[4, 1] , [5, 1], [4, 2], [5, 2]]。

How to fix it?如何解决?

A quick fix is inserting items to the front, instead of to the back.一个快速的解决方法是将项目插入前面,而不是后面。 Instead of set.add(obj) use:而不是set.add(obj)使用:

set.add(0, obj);

Another option is to reverse the order of iteration so that the second level of recursion uses list A, and the first level uses list B. The initial call to start the recursion would be made from sets.size() and it should count down instead of up:另一种选择是颠倒迭代的顺序,以便第二级递归使用列表 A,第一级使用列表 B。开始递归的初始调用将从sets.size()进行,它应该倒计时上:

    return _cartesianProduct(sets.size() - 1, sets);
...
            for (ArrayList<Double> set : _cartesianProduct(index - 1, sets)) {

Yet another option is changing the recursion so that the product is built on the way down the recursion - "outside in" - instead of on the way out.另一种选择是改变递归,使产品建立在递归的下层——“由外而内”——而不是出路。 This is the approach taken in another answer to the question you link to: https://stackoverflow.com/a/9496234/318758这是您链接到的问题的另一个答案中采用的方法: https://stackoverflow.com/a/9496234/318758

If order matters, you can rewrite your code as follows:如果顺序很重要,您可以按如下方式重写代码:

ArrayList<Double> l1 = new ArrayList<>(Arrays.asList(1.0, 2.0, 3.0));
ArrayList<Double> l2 = new ArrayList<>(Arrays.asList(4.0, 5.0, 6.0));
List<List<Double>> cartesianProduct = Stream.of(l1, l2)
        // represent each list element as a singleton list
        .map(list -> list.stream().map(Collections::singletonList)
                // List<List<Double>>
                .collect(Collectors.toList()))
        // intermediate output
        //[[1.0], [2.0], [3.0]]
        //[[4.0], [5.0], [6.0]]
        .peek(System.out::println)
        // summation of pairs of inner lists
        .reduce((list1, list2) -> list1.stream()
                // combinations of inner lists
                .flatMap(inner1 -> list2.stream()
                        // merge two inner lists into one
                        .map(inner2 -> Stream.of(inner1, inner2)
                                .flatMap(List::stream)
                                .collect(Collectors.toList())))
                // list of combinations
                .collect(Collectors.toList()))
        // returns List<List<Double>>, otherwise an empty list
        .orElse(Collections.emptyList());
// final output
cartesianProduct.forEach(System.out::println);
//[1.0, 4.0]
//[1.0, 5.0]
//[1.0, 6.0]
//[2.0, 4.0]
//[2.0, 5.0]
//[2.0, 6.0]
//[3.0, 4.0]
//[3.0, 5.0]
//[3.0, 6.0]

See also: Cartesian product of an arbitrary number of sets另请参阅:任意数量集合的笛卡尔积

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM