【问题标题】:Most efficient way of combining elements of lists of items into sets of combinations?将项目列表的元素组合成组合集的最有效方法?
【发布时间】:2012-05-17 12:15:32
【问题描述】:

假设我们给出了一些项目的列表,比如字符串。

list 1: "a", "b", "c"

list 2: "d", "e", "f"

list 3: "1", "2", "3"


results: (a, d, 1), (a, d, 2), ... (c, f, 3)

(实际用例与字符串等无关,这只是一个模型)

我写了一个递归方法来做它,但我对它不满意,因为它创建了很多被扔掉的临时集(是的,我知道在 java 中创建对象很便宜,通常 cpu 指令比 malloc 在C(来源:Java Concurrency in Action,p241),eden GC 很便宜,等等等等。幽默我:)。

void combine(List<List<String>> itemLists, List<Set<String>> combinations, Set<String> partial) {
    if (itemLists == null || itemLists.isEmpty()) return;
    List<String> items = itemLists.get(0);
    for (String s : items) {
        Set<String> tmpSet = new HashSet<>(partial);
        tmpSet.add(s);
        if (itemLists.size() == 0) //termination test
            combinations.add(tmpSet);
        else
            combine(itemLists.subList(1, itemLists.size()), combinations, tmpSet);
    }
}

那么,你会怎么做呢?

edit:明确地说,我不想创建排列。我想创建 sizeof(list of lists) 大的集合。

【问题讨论】:

  • 嗯,你可以循环执行...(不需要递归)。
  • 列表是否包含相同数量的元素?
  • 您想将所有集合保存在内存中吗?或者您正在搜索结果集中的特定组合或属性?
  • @Paul Vargas - 这不是我想要的。查看我的编辑并查看我的代码。
  • Here is a short answer, here a longer one with Iterator and Iterable。关键字是“笛卡尔积”,什么是最有效的?就大小而言,我想,既然您谈论 GC?还是速度?

标签: java algorithm collections cartesian-product


【解决方案1】:

您正在寻找的是“笛卡尔积”。

如果您可以使用 Sets 而不是 Lists,您可以使用 Sets.cartesianProduct。当您遍历生成的列表时,仍然分配了一些垃圾......但几乎没有其他方法那么多。

(请注意,作为一种常见的库方法,它已经过非常详尽的测试,因此您可以对它更有信心,而不是从 SO 中粘贴数十行代码。)

仅供参考,Lists.cartesianProduct 也有一个 request,但我认为没有人在努力。

【讨论】:

    【解决方案2】:

    假设列表的数量是可变的并且这些列表的大小也是可变的,您想要一个包含所有可能集合的列表,其中包含每个提供的列表中的一个值。对吗?

    那么是这样的吗?

    static List<Set<String>> combine(List<List<String>> itemLists)
    {
        // Calculate how many combinations we'll need to build
        int remainingCombinations = itemLists.get(0).size();
        for(int i=1; i<itemLists.size(); i++)
        {
            remainingCombinations *= itemLists.get(i).size();
        }
    
        List<Set<String>> allSets = new ArrayList<Set<String>>();
    
        // Generate this combination
        for (;remainingCombinations > 0; remainingCombinations --)
        {
            Set<String> currentSet = new HashSet<String>();
            int positionInRow = remainingCombinations;
    
            // Pick the required element from each list, and add it to the set.
            for(int i=0; i<itemLists.size(); i++)
            {
                int sizeOfRow = itemLists.get(i).size();
                currentSet.add(itemLists.get(i).get(positionInRow % sizeOfRow));
                positionInRow /= sizeOfRow;
            }
    
            allSets.add(currentSet);
        }
        return allSets;
    }
    

    【讨论】:

      【解决方案3】:

      这样更有效:采用与计数相同的方式进行处理(每个“位置”都是您的列表之一,每个可以进入该位置的“数字”都是您列表的一个元素):

      List<Set<String>> combine( List<List<String>> input ){
      
          final int n = input.size();
          int[] index = new int[n];
      
          List<Set<Sting>> result = new List<>();
      
          int position = 0;
          while( position < n ){ // "overflow" check
      
              // Add set to result.
              Set<String> set = new HashSet<>();
              for( int i=0; i<n; i++ )
                  set.add( input.get(i).get( index[i] ) );
              result.add( set );
      
              // Now the "hard" part: increment the index array
              position = 0;
              while( position < n ){
      
                  if( index[ position ] < input.get( position ).size() ){
                      index[position]++;
                      break;
                  }
                  else // carry
                      index[ position++ ] = 0;
              }
          }
          return result;
      }
      

      (未测试,可能有一些错误,但主要思想就在那里)。 一般来说,递归比迭代慢。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-12
        • 1970-01-01
        • 2014-10-01
        • 2017-09-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多