【问题标题】:Generate Combinations of generic list生成通用列表的组合
【发布时间】:2013-10-04 18:47:36
【问题描述】:

我需要从另一个包含所有可能组合的列表中创建一个列表。在研究可能的解决方案时,我发现了许多有趣的方法,但似乎都是根据提供的记录数生成结果。我需要将组合增加到最大阈值。

即考虑以下数组

1,2,3,4,5

我需要结果看起来类似于(在本例中阈值为 3)

1
1,2
1,2,3
1,2,4
1,2,5
1,3,4
2,3,5... etc

实际上,数据将是 IEnumerable。我使用了一个简单的 int[] 来说明想要的结果。

【问题讨论】:

标签: c# linq combinations combinatorics cartesian-product


【解决方案1】:

我的解决方案使用简单的递归算法来创建组合:

  • 当我们遍历序列时,我们可以立即返回一个仅保存当前值的序列。我编写了一个简单的扩展方法来为单个项目创建一个 IEnumerable。

  • 接下来我们递归地生成剩余元素的所有组合,阈值减1,并将它们中的每一个与当前值组合。

我假设元素不应重复(即 { 1, 1 } 或 { 1, 2, 1 } 是不允许的)。如果您想允许重复元素,您可以删除 remaining 变量并在对 GetCombinations 的递归调用中将其替换为 values

请注意yield 关键字的使用。这意味着代码使用延迟执行。在实际枚举结果之前无需存储任何中间结果。

public static IEnumerable<IEnumerable<T>> GetCombinations<T>(IEnumerable<T> values, int threshold)
{
    var remaining = values;

    foreach (T value in values)
    {
        yield return value.Yield();

        if (threshold < 2)
        {
            continue;
        }

        remaining = remaining.Skip(1);

        foreach (var combination in GetCombinations(remaining, threshold - 1))
        {
            yield return value.Yield().Concat(combination);
        }
    }
}

public static IEnumerable<T> Yield<T>(this T item)
{
    yield return item;
}

对于整数数组 { 1, 2, 3, 4, 5 },输出为:

1
1, 2
1, 2, 3
1, 2, 4
1, 2, 5
1, 3
1, 3, 4
1, 3, 5
1, 4
1, 4, 5
1, 5
2
2, 3
2, 3, 4
2, 3, 5
2, 4
2, 4, 5
2, 5
3
3, 4
3, 4, 5
3, 5
4
4, 5
5

【讨论】:

  • 我更喜欢这个 :)
【解决方案2】:

假设您已经有一个解决方案来找到特定计数的组合(您已经说过您有),让我们说一下签名:

public static IEnumerable<IEnumerable<T>> Combinations<T>(
    IList<T> source, int count)

然后你可以通过调用它N次轻松获得所有计数的组合:

public static IEnumerable<IEnumerable<T>> Combinations<T>(IList<T> source)
{
    return Enumerable.Range(0, source.Count)
            .SelectMany(i => Combinations(source, i));
}

【讨论】:

    【解决方案3】:

    这是一种解决方案:

    public static IEnumerable<T[]> Combinations<T>(IEnumerable<T> items, int threshold)
    {
        var comboBuilder = new List<T>();
        foreach (var combo in EnumerateCombos(items, comboBuilder, 0, threshold))
        {
            yield return combo;
        }
    }
    private static IEnumerable<T[]> EnumerateCombos<T>(IEnumerable<T> items, List<T> currentCombo, 
                                                       int startIndex, int threshold)
    {
        if (currentCombo.Count >= threshold) { yield break; }
    
        for (int i = startIndex; i < items.Count(); i++)
        {
            //Skip past the items we've already gone through in the current combo:
            var item = items.Skip(i).First();
    
            //Create a new combination with the next available item:
            currentCombo.Add(item);
            yield return currentCombo.ToArray();
    
            //Repeat the process with the rest of the remaining items:
            foreach (var combo in EnumerateCombos(items, currentCombo, i + 1, threshold))
            {
                yield return combo;
            }
    
            //Recursion cleanup:
            currentCombo.RemoveAt(currentCombo.Count - 1);
        }
    }
    

    【讨论】:

    • 非常有趣的解决方案。
    猜你喜欢
    • 2019-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-05
    • 2020-01-28
    相关资源
    最近更新 更多