在 Java 中生成所有列表 n 级深度的组合

Posted

技术标签:

【中文标题】在 Java 中生成所有列表 n 级深度的组合【英文标题】:Generating All Combinations of List n Levels Deep in Java 【发布时间】:2019-08-24 12:09:52 【问题描述】:

我正在使用以下代码生成大小 s 组合的列表:

public static <T extends Comparable<? super T>> List<List<T>> combinations(List<T> items, int size) 
        if (size == 1) 
            List<List<T>> result = new ArrayList<>();
            for (T item : items) 
                result.add(Collections.singletonList(item));
            
            return result ;
        
        List<List<T>> result = new ArrayList<>();
        for (int i=0; i <= items.size() - size; i++) 
            T firstItem = items.get(i);
            List<List<T>> additionalItems = combinations(items.subList(i+1, items.size()), size-1) ;
            for (List<T> additional : additionalItems) 
                List<T> combination = new ArrayList<>();
                combination.add(firstItem);
                combination.addAll(additional);
                result.add(combination);
            
        
        return result ;

给定一个列表,其值为1, 2 and 3,大小为2

List<Integer> items = new ArrayList<Integer>();
items.add(1);
items.add(2);
items.add(3);

combinations(items, 2)

这会产生以下组合:

[1, 2]
[1, 3]
[2, 3]

我正在尝试获取此输出并生成第三个列表,其中先前输出中的三行中的每一行现在都与其他行结合在一起 - 只是这次对顺序敏感并且最多 'd' 层次深。我期待类似于以下输出的结果:

1 级深度:

[1, 2]
[1, 3]
[2, 3]

2 级深度:

[1, 2], [1, 3]
[1, 2], [2, 3]
[1, 3], [2, 3]
[1, 3], [1, 2]
[2, 3], [1, 2]
[2, 3], [1, 3]

3 级深度:

[[1, 2], [1, 2], [1, 3]]
[[1, 2], [1, 2], [2, 3]]
[[1, 2], [1, 3], [1, 2]]
[[1, 2], [1, 3], [1, 3]]
[[1, 2], [1, 3], [2, 3]]
[[1, 2], [2, 3], [1, 2]]
[[1, 2], [2, 3], [1, 3]]
[[1, 2], [2, 3], [2, 3]]
[[1, 3], [1, 2], [1, 2]]
[[1, 3], [1, 2], [1, 3]]
[[1, 3], [1, 2], [2, 3]]
[[1, 3], [1, 3], [1, 2]]
[[1, 3], [1, 3], [2, 3]]
[[1, 3], [2, 3], [1, 2]]
[[1, 3], [2, 3], [1, 3]]
[[1, 3], [2, 3], [2, 3]]
[[2, 3], [1, 2], [1, 2]]
[[2, 3], [1, 2], [1, 3]]
[[2, 3], [1, 2], [2, 3]]
[[2, 3], [1, 3], [1, 2]]
[[2, 3], [1, 3], [1, 3]]
[[2, 3], [1, 3], [2, 3]]
[[2, 3], [2, 3], [1, 2]]
[[2, 3], [2, 3], [1, 3]]

4 级深度:

[[1, 2], [1, 2], [1, 2], [1, 3]]
[[1, 2], [1, 2], [1, 2], [2, 3]]
[[1, 2], [1, 2], [1, 3], [1, 2]]
[[1, 2], [1, 2], [1, 3], [1, 3]]
[[1, 2], [1, 2], [1, 3], [2, 3]]
[[1, 2], [1, 2], [2, 3], [1, 2]]
[[1, 2], [1, 2], [2, 3], [1, 3]]
[[1, 2], [1, 2], [2, 3], [2, 3]]
[[1, 2], [1, 3], [1, 2], [1, 2]]
[[1, 2], [1, 3], [1, 2], [1, 3]]
[[1, 2], [1, 3], [1, 2], [2, 3]]
[[1, 2], [1, 3], [1, 3], [1, 2]]
[[1, 2], [1, 3], [1, 3], [1, 3]]
[[1, 2], [1, 3], [1, 3], [2, 3]]
[[1, 2], [1, 3], [2, 3], [1, 2]]
[[1, 2], [1, 3], [2, 3], [1, 3]]
[[1, 2], [1, 3], [2, 3], [2, 3]]
[[1, 2], [2, 3], [1, 2], [1, 2]]
[[1, 2], [2, 3], [1, 2], [1, 3]]
[[1, 2], [2, 3], [1, 2], [2, 3]]
[[1, 2], [2, 3], [1, 3], [1, 2]]
[[1, 2], [2, 3], [1, 3], [1, 3]]
[[1, 2], [2, 3], [1, 3], [2, 3]]
[[1, 2], [2, 3], [2, 3], [1, 2]]
[[1, 2], [2, 3], [2, 3], [1, 3]]
[[1, 2], [2, 3], [2, 3], [2, 3]]
[[1, 3], [1, 2], [1, 2], [1, 2]]
[[1, 3], [1, 2], [1, 2], [1, 3]]
[[1, 3], [1, 2], [1, 2], [2, 3]]
[[1, 3], [1, 2], [1, 3], [1, 2]]
[[1, 3], [1, 2], [1, 3], [1, 3]]
[[1, 3], [1, 2], [1, 3], [2, 3]]
[[1, 3], [1, 2], [2, 3], [1, 2]]
[[1, 3], [1, 2], [2, 3], [1, 3]]
[[1, 3], [1, 2], [2, 3], [2, 3]]
[[1, 3], [1, 3], [1, 2], [1, 2]]
[[1, 3], [1, 3], [1, 2], [1, 3]]
[[1, 3], [1, 3], [1, 2], [2, 3]]
[[1, 3], [1, 3], [1, 3], [1, 2]]
[[1, 3], [1, 3], [1, 3], [2, 3]]
[[1, 3], [1, 3], [2, 3], [1, 2]]
[[1, 3], [1, 3], [2, 3], [1, 3]]
[[1, 3], [1, 3], [2, 3], [2, 3]]
[[1, 3], [2, 3], [1, 2], [1, 2]]
[[1, 3], [2, 3], [1, 2], [1, 3]]
[[1, 3], [2, 3], [1, 2], [2, 3]]
[[1, 3], [2, 3], [1, 3], [1, 2]]
[[1, 3], [2, 3], [1, 3], [1, 3]]
[[1, 3], [2, 3], [1, 3], [2, 3]]
[[1, 3], [2, 3], [2, 3], [1, 2]]
[[1, 3], [2, 3], [2, 3], [1, 3]]
[[1, 3], [2, 3], [2, 3], [2, 3]]
[[2, 3], [1, 2], [1, 2], [1, 2]]
[[2, 3], [1, 2], [1, 2], [1, 3]]
[[2, 3], [1, 2], [1, 2], [2, 3]]
[[2, 3], [1, 2], [1, 3], [1, 2]]
[[2, 3], [1, 2], [1, 3], [1, 3]]
[[2, 3], [1, 2], [1, 3], [2, 3]]
[[2, 3], [1, 2], [2, 3], [1, 2]]
[[2, 3], [1, 2], [2, 3], [1, 3]]
[[2, 3], [1, 2], [2, 3], [2, 3]]
[[2, 3], [1, 3], [1, 2], [1, 2]]
[[2, 3], [1, 3], [1, 2], [1, 3]]
[[2, 3], [1, 3], [1, 2], [2, 3]]
[[2, 3], [1, 3], [1, 3], [1, 2]]
[[2, 3], [1, 3], [1, 3], [1, 3]]
[[2, 3], [1, 3], [1, 3], [2, 3]]
[[2, 3], [1, 3], [2, 3], [1, 2]]
[[2, 3], [1, 3], [2, 3], [1, 3]]
[[2, 3], [1, 3], [2, 3], [2, 3]]
[[2, 3], [2, 3], [1, 2], [1, 2]]
[[2, 3], [2, 3], [1, 2], [1, 3]]
[[2, 3], [2, 3], [1, 2], [2, 3]]
[[2, 3], [2, 3], [1, 3], [1, 2]]
[[2, 3], [2, 3], [1, 3], [1, 3]]
[[2, 3], [2, 3], [1, 3], [2, 3]]
[[2, 3], [2, 3], [2, 3], [1, 2]]
[[2, 3], [2, 3], [2, 3], [1, 3]]

请注意在 2 个级别的深度组合 [1, 2], [1, 2] 是如何不生成的,因为在该集合之间、之前或之后没有一组不同的数字。然而,在 3 层深度,我们生成组合 [1, 2], [1, 3], [1, 2],因为组合 [1, 3] 存在于两对 [1, 2] 之间。

同样,在 4 层深度,我们生成序列[1, 2], [1, 3], [1, 2], [1, 2],它等同于序列[1, 2], [1, 3], [1, 2],因为在[1, 2], [1, 3], [1, 2] 之后还有额外的[1, 2] 序列。我们不会在 4 层深度生成序列 [1, 2], [1, 2], [1, 2], [1, 2],因为这种组合本质上等同于 [1, 2],因为在组合 [1, 2] 之前或之后没有新的数字集。

简而言之,我如何组合一个数字列表列表 - 直到任意数量的深度级别(1-4 仅用作示例)但这次结果是顺序敏感 (所以 [1, 2], [1, 3] 不等于 [1, 3], [1, 2])?结果可能会存储在List&lt;List&lt;List&lt;Integer&gt;&gt;&gt;

我在 *** 上进行了搜索,并看到了几个关于生成组合的线程(例如 this one 和 this one),但没有解决上述确切情况。

谢谢

【问题讨论】:

“前一个输出中的每一行现在都与其他每一行相结合”似乎不清楚。 “每隔一行”到底是什么? (提供完整而不是编辑的输出可能对此有所帮助,但理想情况下,所需的方法将被更仔细地定义。) 我相信您正在寻找 Apriori 算法。 en.wikipedia.org/wiki/Apriori_algorithm @גלעדברקן 更新的问题,澄清和完整的输出。 【参考方案1】:

我相信我制作了您想要的东西。代码分为四个单独的方法(如果它必须只有 1,你没有做任何区分):

public static <T extends Comparable<? super T>> List<List<List<T>>> level(List<List<T>> items, int level) 
    List<List<List<T>>> result = new ArrayList<>();
    if(level == 1) 
        for(List<T> item : items) 
            result.add(Collections.singletonList(item));
        
        return result;
    

    for(int i = 0; i < level; i++) 
        if(i == 0) 
            for(List<T> item : items)
                result.add(Collections.singletonList(item));
            continue;
        

        List<List<List<T>>> newResult = new ArrayList<>();
        for(List<List<T>> item : result) 
            List<List<List<T>>> combined = new ArrayList<>();
            List<T> first = item.get(0);
            for(int j = 0; j < items.size(); j++) 
                List<List<T>> current = new ArrayList<>();
                List<T> it = items.get(j);
                current.addAll(item);
                current.add(it);

                combined.add(current);
            

            newResult.addAll(combined);
        

        result = newResult;
    

    clean(result);
    return result;

这是大多数算法的作用。首先,就像在您提供的函数中一样,它会检查级别是否为 1,在这种情况下,您可以只返回一个列表列表,就像在给定的方法中一样。之后,我们循环遍历我们拥有的许多级别。我们首先检查当前级别是否为 1,在这种情况下,它将执行与调用级别为 1 的方法相同的操作。接下来,我们创建一个名为 newResult 的新列表。这个变量基本上是一个临时值result,以防止并发修改异常。然后我们遍历result 中已经存在的每个值。我们根据我们拥有的任何值创建一些新值,并将它们添加到combined。然后我们将combined添加到newResult中,算法基本结束。现在,当所有值都相同时,这些循环不计算在内,例如,[1, 2], [1, 2], [1, 2], [1, 2]。所以我们调用clean 方法来删​​除这些情况。

public static <T extends Comparable<? super T>> void clean(List<List<List<T>>> list) 
    List<List<List<T>>> removals = new ArrayList<>();
    for(List<List<T>> item : list) 
        if(!check(item))
            removals.add(item);
    
    for(List<List<T>> item : removals) 
        list.remove(item);
    

此方法遍历给定列表中的所有内容,并在元素上运行check 方法。该方法将在下面进一步解释。如果元素无效,则将其标记为删除,并在下一个循环中删除。

public static <T extends Comparable<? super T>> boolean check(List<List<T>> list) 
    if(list.size() < 2) return true;

    for(int i = 1; i < list.size(); i++) 
        List<T> previous = list.get(i-1);
        List<T> item = list.get(i);

        if(notEqual(previous, item))
            return true;
        
    

    return false;

此循环通过将一个列表与另一个列表进行比较来检查给定列表是否有效,直到找到两个不相同的列表。发生这种情况时,列表是有效的,并返回 true。如果不是,则永远不会返回,会跳出循环,返回 false。

public static <T extends Comparable<? super T>> boolean notEqual(List<T> a, List<T> b) 
    for(int i = 0; i < Math.min(a.size(), b.size()); i++) 
        T ao = a.get(i);
        T bo = b.get(i);

        if(ao.compareTo(bo) != 0)
            return true;
    

    return false;

此方法接受两个输入列表,并检查其中的元素是否不相等。它遍历两个列表,获取相同索引处的元素,并将它们相互比较。如果不相等则返回true,否则结束循环并返回false。

请注意,这只是一个概念证明,而不是最终版本。它的很多方面肯定可以改进,但这是您所要求的工作版本。

Link to working version on jDoodle

如果您对它的任何方面有任何疑问,或者想要澄清任何事情,请不要犹豫!

编辑: 我已经修改了算法以包含您所要求的内容。这是新代码:

public static <T extends Comparable<? super T>> List<List<List<T>>> level(List<List<T>> items, int minLevel, int maxLevel) 
    List<List<List<T>>> result = new ArrayList<>();

    for(int i = minLevel; i < maxLevel+1; i++) 
        result.addAll(level(items, i));
    

    return result;

这是允许您指定所需级别范围的重载方法。给定最小和最大级别,它将返回一个新列表,其中包含该范围内的所有级别,包括在内。正如你所说,作为一个简单的循环,它相对微不足道。

public static <T extends Comparable<? super T>> List<List<List<T>>> level(List<List<T>> items, int level) 
    List<List<List<T>>> result = new ArrayList<>();
    if(level == 1) 
        for(List<T> item : items) 
            result.add(Collections.singletonList(item));
        
        return result;
    

    for(int i = 0; i < level; i++) 
        if(i == 0) 
            for(List<T> item : items)
                result.add(Collections.singletonList(item));
            continue;
        

        List<List<List<T>>> newResult = new ArrayList<>();
        for(List<List<T>> item : result) 
            if(item.size() < i)
                continue;

            List<List<List<T>>> combined = new ArrayList<>();
            List<T> first = item.get(0);
            for(int j = 0; j < items.size(); j++) 
                List<List<T>> current = new ArrayList<>();
                List<T> it = items.get(j);
                current.addAll(item);
                current.add(it);

                combined.add(current);
            

            newResult.addAll(combined);
        

        result = newResult;
    

    List<List<List<T>>> removals = new ArrayList<>();
    for(List<List<T>> item : result) 
        if(!check(item))
            removals.add(item);
    
    for(List<List<T>> item : removals) 
        result.remove(item);
    

    return result;

这是修改后的方法。我删除了clean 方法,并将其放在level 方法中,因为它只被调用了一次。我不认为这真的有可能,至少在算法期间使用当前代码运行 clean 方法,因为在这个时间点,它的工作方式是它为给定级别生成所有可能的组合,然后转到下一个。如果删除了相同的组合,则在下一级,这些组合将不会被添加。

这是一个例子: 假设我有[1, 2], [1, 3], [2, 3]。如果我进入第二级,我会在你的问题中指定组合。很明显吧?好吧,如果我接着进入第 3 级,仅使用第 2 级的结果,我会错过所有包含[1, 2], [1, 2] [...] 的组合,因为它不在给定的列表中。这是算法的问题,肯定可以改进。

我计划进一步重构它,让它在算法内部进行检查,但这样做可能需要我很长时间。

New working version in jDoodle

编辑 2: 在算法中加入clean 方法实际上比我最初想象的要简单得多。这是带有几个 cmets 的新代码:

public static <T extends Comparable<? super T>> List<List<List<T>>> level(List<List<T>> items, int level) 
    List<List<List<T>>> result = new ArrayList<>();
    for(int i = 0; i < level; i++) 
        if(i == 0)  // If level is 0, we can just add the items as singleton lists to the result
            for(List<T> item : items)
                result.add(Collections.singletonList(item));
            continue;
        

        List<List<List<T>>> newResult = new ArrayList<>(); // Temporary items that will be added
        for(List<List<T>> item : result) 
            if(item.size() < i) // Make sure we are manipulating items that are on the previous level
                continue;

            List<List<List<T>>> combined = new ArrayList<>(); // The temporary values for this specific item
            for(int j = 0; j < items.size(); j++) 
                List<List<T>> current = new ArrayList<>(); // The current list with the value
                current.addAll(item); // Add the current items from result to the list
                current.add(items.get(j)); // Add the current item from items to the list

                if (i == level-1 && !check(current))  // If this is the last level, and the current list shouldn't be added, skip adding
                    continue;
                

                combined.add(current); // Add the current list to the combined values
            

            newResult.addAll(combined); // Add all of the lists in combined to the new result
        

        result = newResult; // Make result equal to the new result
    

    return result;

现在它所做的是,当向列表中添加新组合时,它首先检查当前级别是否是最后一个级别。如果是这样,它实际上会检查列表,如果它无效,它会跳过实际添加它。

我再次计划以更智能的格式完全重写算法,但这段代码现在完全可以工作。

Working version on jDoodle

【讨论】:

我相信这是正确的答案,感谢每一段代码后的解释。两个简单的问题:1) 是否可以将clean() 方法合并到算法本身中,而不是在单独的函数中删除重复数据? 2) 最终列表List&lt;List&lt;List&lt;T&gt;&gt;&gt; 应包含指定范围内的所有级别。如果minLevel = 1 和maxLevel = 4,那么它将包含级别 1-4。我相信通过使用参数minLevelmaxLevel 并融合List&lt;List&lt;List&lt;T&gt;&gt;&gt; 中的所有列表创建一个名为'levels' 的新方法,实现这一点相对简单。 尝试编辑代码,直到你得到它的工作,@Alan Cook @AlanCook 我已经实现了你所要求的一切,但我仍将完全重写它,以便在填写列表时更加智能。

以上是关于在 Java 中生成所有列表 n 级深度的组合的主要内容,如果未能解决你的问题,请参考以下文章

Hive UDF 从列表中生成所有可能的有序组合

如何从所有排列中生成所有可能的组合?

Javascript - 在单个数组中生成所有元素组合(成对)

R从n个元素的字符向量中生成大小为m的所有可能组合[重复]

在Matlab中生成包含给定集合中至少一个元素的所有组合

在 Matlab 中生成矩阵的所有可能组合