将项目列表的元素组合成多组合的最有效方法?

时间:2012-05-09 00:27:53

标签: java algorithm collections cartesian-product

我们假设我们列出了一些项目清单,比如说Strings。

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中的对象创建很便宜,通常比malloc中的cpu指令更少) 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);
    }
}

那么,你会怎么做呢?

编辑:要明确,我不想创建排列。我想创建sizeof(列表列表)大的集合。

3 个答案:

答案 0 :(得分:3)

您正在寻找的是“笛卡儿产品”。

如果您使用“集”而不是“列表”,则可以使用Sets.cartesianProduct。在迭代生成的列表时仍然会分配一些垃圾......但不会像其他方法那样多。

(请注意,作为一种常见的库方法,它经过了非常详尽的测试,因此您可以比使用SO中的几十行代码更有信心。)

仅供参考Lists.cartesianProduct也有request,但我认为没有人在研究它。

答案 1 :(得分:1)

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

那样的东西呢?

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;
}

答案 2 :(得分:1)

这样效率更高:以与计算工作相同的方式处理它(每个“位置”是您的一个列表,每个“数字”可以进入该位置是列表的一个元素):

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;
}

(未经测试,可能有一些错误,但主要的想法是那里)。 通常,递归比迭代慢。