【问题标题】:Get all the combinations of List<List<int>> (with partial results, too) in C#在 C# 中获取 List<List<int>> 的所有组合(也有部分结果)
【发布时间】:2011-04-27 13:55:05
【问题描述】:

我需要一个有效的算法来获取整数列表的所有可用组合。我也需要部分结果。

一个例子:

{1, 2, 3}
{4, 5}
{6, 7, 8}

我需要得到:

1
2
3
1/4
1/5
2/4
2/5
3/4
3/5
1/4/6
1/4/7
1/4/8
1/5/6
1/5/7
1/5/8
2/4/6
2/4/7
2/4/8
2/5/6
2/5/7
2/5/8
3/4/6
3/4/7
3/4/8
3/5/6
3/5/7
3/5/8

如果可能的话,我需要它以最快的方式。我已经有了一个算法,但我想知道是否有更好的替代方案。

谢谢。

编辑: 这是我当前的代码。对不起意大利的cmets。

// Istanzia una lista per contenere le liste di valori
List<List<int>> allLists = new List<List<int>>();

... CODE TO FILL THE LISTS ...

// Esegue un ciclo fino al numero di liste recuperate
for (int listIndex = 0; listIndex < allLists.Count; listIndex++)
{
    // Istanzia una lista per contenere le liste di valori fino allo 
    // step corrente
    List<List<int>> stepLists = new List<List<int>>();

    // Esegue un ciclo sulle liste fino allo step corrente
    for (int stepListIndex = 0; stepListIndex <= listIndex; stepListIndex++)
    {
        // Aggiunge la lista a quelle dello step corrente
        stepLists.Add(allLists[stepListIndex]);
    }

    // Esegue il prodotto vettoriale delle liste specificate
    List<List<int>> crossLists = 
        Mathematics.CrossProduct(stepLists, new List<int>());

    // Carica l'elenco delle combinazioni
    CombinationsCollection allCombinations = 
        new CombinationsCollection(Kernel);
    allCombinations.Load();

    // Esegue un ciclo su ciascuna lista recuperata
    foreach (List<int> crossList in crossLists)
    {
    }
}

... OTHER CODE ...

public static List<List<int>> CrossProduct(
    List<List<int>> lists, 
    List<int> root)
{
    // Istanzia delle liste per contenere le combinazioni
    List<List<int>> results = new List<List<int>>();

    // Se ce n'è almeno una
    if (lists.Count > 0)
    {
        // Recupera la prima lista
        List<int> list = (List<int>)lists[0];

        // Se è rimasta solo una lista
        if (lists.Count == 1)
        {
            // Esegue un ciclo su tutti i valori
            foreach (int value in list)
            {
                // Aggiunge un risultato con radice e valore
                results.Add(new List<int>(root) { value });
            }
        }
        else
        {
            // Esegue un ciclo su ogni valore della lista
            foreach (int value in list)
            {
                // Aggiunge ai risultati la prosecuzione del prodotto 
                // vettoriale dalla lista successiva
                results.AddRange(CrossProduct(
                    lists.GetRange(1, lists.Count - 1), 
                    new List<int>(root) { value })
                );
            }
        }
    }

    return results;
}

【问题讨论】:

  • 您似乎没有在所需的输出中包含所有可能的组合。例如 4, 5, 6, 7, 8, 1/6, 1/7, ... 为什么会这样?
  • 抱歉,所有可能的组合都会按顺序排列。
  • 所以澄清一下,如果“x”表示“笛卡尔积”和“|”表示“连接”,那么您想要获取一系列序列 { A, B, C } 并生成序列 (A) | (AxB) | (AxBxC),是吗?
  • 另外,我很确定您不想要“可能的最快方式”,因为您可能不愿意用在液冷超级计算机上运行的经过严格优化的机器代码编写算法。要求“最快”的方式做某事是行不通的。如果您有一个性能预算并且您无法满足它,那么这是该问题的工程标准;说明您的预算是多少以及超出了多少。
  • 显然,意思是“C#中最快的方式”。我不知道生产时将在哪台机器上托管代码。

标签: c# algorithm combinations


【解决方案1】:

您需要一个返回列表部分结果的笛卡尔积的方法(正如您在标题中提到的那样)。这是来自 Eric Lippert 的 CartesianProduct 方法扩展的变体 根据您的要求获得部分结果:

public static IEnumerable<IEnumerable<T>> CrossProduct<T>(
     this IEnumerable<IEnumerable<T>> sequences)
{
    IEnumerable<IEnumerable<T>> accumulator = new[] { Enumerable.Empty<T>() };
    var result = new List<IEnumerable<T>>();
    var firstSequence = true;
    foreach (var sequence in sequences)
    {
        var local = new List<IEnumerable<T>>();
        foreach (var accseq in accumulator)
        {
            if (!firstSequence)
                result.Add(accseq);

            foreach (var item in sequence)
                local.Add(accseq.Concat(new[] { item }));
        }
        firstSequence = false;
        accumulator = local;
    }

    return result.Concat(accumulator);
}

对于您提供的输入数据:

var items = new[] { 
    new[] { 1, 2, 3 }, 
    new[] { 4, 5 }, 
    new[] { 7, 8, 9 } 
};
var product = items.CrossProduct();

product 变量将包含您想要的结果。

【讨论】:

  • 这是一个很棒的方法!谢谢!
【解决方案2】:

我相信您正在寻找的是您的序列集的幂集“前缀集”的每个元素的笛卡尔积。让我们分解一下:

首先,你有一组输入序列:

IEnumerable<IEnumerable<int>> inputSet = new[] {new[] {1,2,3}, new[] {4,5}, new[] {6,7,8}};

inputSet的“前缀集”是:

{ {}, {1,2,3}, { {1,2,3},{4,5} }, { {1,2,3},{4,5},{6,7 ,8} } }

一组序列的笛卡尔积产生每个序列中一项的所有组合。

我相信你正在寻找的是:(伪代码)

foreach (element in the above prefix set)
{
  Print(cartesian product of the sequences in this element);
}

以下是我用来生成笛卡尔积、幂集等的扩展方法:

public static class CombinatorialExtensionMethods {
    public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences) 
    { 
        IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>() }; 
        return sequences.Aggregate( 
            emptyProduct, 
            (accumulator, sequence) =>  
            from accseq in accumulator  
            from item in sequence  
            select accseq.Concat(new[] {item}));                
    }

    public static IEnumerable<IEnumerable<T>> CartesianPower<T>(this IEnumerable<T> sequence, int power) 
    { 
        var sequences = Enumerable.Repeat<IEnumerable<T>>(sequence,power);
        return sequences.CartesianProduct<T>();
    }

    public static IEnumerable<IEnumerable<T>> Permute<T>(this IEnumerable<T> seq, int k)
    { 
        var sequences = Enumerable.Repeat<IEnumerable<T>>(seq,k);
        IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>() }; 
        return sequences.Aggregate( 
            emptyProduct, 
            (accumulator, sequence) =>  
            from accseq in accumulator  
            from item in sequence
            where !accseq.Contains(item)
            select accseq.Concat(new[] {item}));
    }

    public static IEnumerable<IEnumerable<int>> Choose(this IEnumerable<int> seq, int k)
    { 
        var sequences = Enumerable.Repeat<IEnumerable<int>>(seq,k);
        IEnumerable<IEnumerable<int>> emptyProduct = new[] { Enumerable.Empty<int>() }; 
        return sequences.Aggregate( 
            emptyProduct, 
            (accumulator, sequence) =>  
            from accseq in accumulator  
            from item in sequence
            where accseq.All(accitem => accitem.CompareTo(item) < 0)
            select accseq.Concat(new[] {item}));
    }

    public static IEnumerable<IEnumerable<T>> Choose<T>(this IEnumerable<T> seq, int k)
    { 
        IEnumerable<int> idxSequence = Enumerable.Range(0, seq.Count());
        IEnumerable<IEnumerable<int>> idxChoose = idxSequence.Choose(k);
        IEnumerable<IEnumerable<T>> result = Enumerable.Empty<IEnumerable<T>>(); 
        foreach (IEnumerable<int> permutation in idxChoose)
        {
            IEnumerable<T> item = Enumerable.Empty<T>();
            foreach (int index in permutation)
            {
                item = item.Concat(new[] { seq.ElementAt(index) });
            }
            result = result.Concat(new[] { item });
        }
        return result;
    }

    public static IEnumerable<IEnumerable<T>> PowerSet<T>(this IEnumerable<T> seq)
    {
        IEnumerable<IEnumerable<T>> result = new[] { Enumerable.Empty<T>() };
        for (int i=1; i<=seq.Count(); i++)
        {
            result = result.Concat(seq.Choose<T>(i));
        }

        return result;
    }
}

使用这些,您的示例代码将是:

IEnumerable<IEnumerable<int>> sequences = new[] {new[] {1,2,3}, new[] {4,5}, new[] {6,7,8}};
IEnumerable<IEnumerable<IEnumerable<int>>> prefixSet = new[] {new[] { Enumerable.Empty<int>() }};

for (int i=0; i<sequences.Count(); i++)
{
    IEnumerable<IEnumerable<int>> prefixSequence = Enumerable.Empty<IEnumerable<int>>();
    for (int j=0; j<=i; j++)
    {
        prefixSequence = prefixSequence.Concat(new[] { sequences.ElementAt(j) });
    }
    prefixSet = prefixSet.Concat(new[] { prefixSequence });
}

foreach (IEnumerable<IEnumerable<int>> item in prefixSet)
{
    Console.WriteLine(item.CartesianProduct<int>());
}

【讨论】:

  • 嗯...在给定样本输入时,幂集的每个元素的笛卡尔积的并集不会产生类似4/6(这是“无效”)的东西吗?
  • @abeln - 为什么 4/6 “无效”?我认为海报只是没有列出所有有效的组合。如果我错了,有人可以改写要求以澄清吗?
  • 我认为 OP 明确列出了所有必需的组合。 4/6 将是“无效的”,因为您缺少第一个列表中的元素。无论如何,这只是我的猜测。
  • @abeln - 我想这是可能的。也许要求是将第一个序列视为“特殊”,这样结果中的每个序列都需要包含第一个序列中的一个元素。
  • 好吧,我认为该模式更通用,即,如果您的组合中有列表 n 的元素,则还需要列表 1..n-1 中的元素。这与示例输出相匹配。
【解决方案3】:

这将产生所需的输出。但不确定与原始编码相比的性能:

private List<List<int>> test(List<List<int>> lists)
{
    List<List<int>> ret = new List<List<int>>();
    ret.AddRange(from first in lists[0] select new List<int>(new int[] { first }));

    List<List<int>> inner = new List<List<int>>();
    inner.AddRange(from first in lists[0] select new List<int>(new int[] { first }));

    for (int i = 1; i < lists.Count;i++ )
    {
        List<int> l = lists[i];

        var newElements =
          from first in inner
          from second in l
          select new List<int>(first.Concat<int>(new int[] { second }));

        ret.AddRange(newElements);
        inner = newElements.ToList();
    }

    return ret;
}

如果调用使用

        List<List<int>> rval = test(
            new List<List<int>>(
                new List<int>[] { 
                    new List<int>(new int[] { 1, 2, 3 }),
                    new List<int>(new int[] { 4, 5 }),
                    new List<int>(new int[] { 6, 7, 8 })
            }));

生成的 rval 包含所需方式的元素。

可能其中的一些 Linq 东西可以重构为更容易或更高效的东西,我对这些东西并不扎实(这就是为什么我在发布它之前对其进行了测试 ;-))

【讨论】:

  • 谢谢!我需要尝试这段代码来比较性能。当然它更紧凑,这是一个不错的奖励! :)
猜你喜欢
  • 1970-01-01
  • 2013-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-27
相关资源
最近更新 更多