【问题标题】:Combinations from different groups in a specific order按特定顺序来自不同组的组合
【发布时间】:2017-07-03 21:26:55
【问题描述】:

我有一个参数列表,每个参数都接受特定范围的输入。我正在创建一个创建每个可能的有效输入的测试。此外,每个组都是可选的(可以完全跳过),因此组合长度不必与列表的长度相同。

输入

List<string[]> temp = new List<string[]>
{
    // The order of these groups is important
    new string[] { "a", "b", "c" },
    new string[] { "d", "e" },
    new string[] { "f", "g", "h" }
};

约束

  1. 每组 0 或 1 项(string[] 以上)
  2. 必须保留List&lt;T&gt; 的顺序

有效组合

  1. a, e, f
  2. a, d, g
  3. c, e, f
  4. b, g
  5. c, f

无效组合

  1. a, b, fab 来自同一组 - 不允许)
  2. a, f, d(顺序错误 - d 必须在 f 之前)

到目前为止,我已经回到我的库,在那里我有一个 Combinations LINQ 方法。

public static class IEnumerableExtensions
{
    // Can be used to get all permutations at a certain level
    // Source: http://stackoverflow.com/questions/127704/algorithm-to-return-all-combinations-of-k-elements-from-n#1898744
    public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int k)
    {
        return k == 0 ? new[] { new T[0] } :
          elements.SelectMany((e, i) =>
            elements.Skip(i + 1).Combinations(k - 1).Select(c => (new[] { e }).Concat(c)));
    }
}

过去我只在单个序列上使用它来执行generate every permutation of URL segments 之类的操作。但我在嵌套列表中使用它时遇到了困难,每个组有一个限制,并且按特定顺序。

我知道我可以通过执行 3 个嵌套循环并使用列表来跟踪已使用的项目来解决这个特殊的难题,但我事先不知道 List&lt;T&gt; 中有多少项目,所以不会'一般情况下是不行的。

如何获得上述输入的所有有效组合?

我更喜欢 LINQ,但会接受任何解决此问题的解决方案。

【问题讨论】:

  • 这需要一个递归函数。您可以从 linq 调用递归方法。一级递归将遍历“a,b,c”。二级将遍历“d,e” 三级将遍历“f,g,h”。
  • @jdweng - 你能举个例子吗?

标签: c# .net linq combinations


【解决方案1】:

使用一些扩展功能,

public static IEnumerable<T> Prepend<T>(this IEnumerable<T> rest, params T[] first) => first.Concat(rest);
public static IEnumerable<T> AsSingleton<T>(this T source) {
    yield return source;
}

你可以写

public static IEnumerable<IEnumerable<T>> ParametersWithEmpty<T>(this IEnumerable<IEnumerable<T>> ParmLists) {
    if (ParmLists.Count() == 0)
        yield break; // empty
    else {
        var rest = ParametersWithEmpty(ParmLists.Skip(1));
        foreach (var p in ParmLists.First()) {
            yield return p.AsSingleton(); // p.Concat(empty)
            foreach (var r in rest)
                yield return r.Prepend(p); // p.Concat(r)
        }
        foreach (var r in rest)
            yield return r; // empty.Concat(r)
    }
}

你可以这样称呼它:

var ans = temp.ParametersWithEmpty();

要在结果中包含所有级别,您必须跳过上述代码中隐含的空案例:

public static IEnumerable<IEnumerable<T>> Parameters<T>(this IEnumerable<IEnumerable<T>> ParmLists) {
    if (ParmLists.Count() == 1)
        foreach (var p in ParmLists.First())
            yield return p.AsSingleton();
    else {
        var rest = Parameters(ParmLists.Skip(1));
        foreach (var p in ParmLists.First()) {
            foreach (var r in rest)
                yield return r.Prepend(p);
        }
    }
}

最后,这是一个替代版本,它可能会更清楚一些,因为它输出的序列就像每个参数列表前面都是空的,但也返回答案中的全空序列。

public static IEnumerable<IEnumerable<T>> ParametersWithEmpty2<T>(this IEnumerable<IEnumerable<T>> ParmLists) {
    if (ParmLists.Count() == 0)
        yield return Enumerable.Empty<T>();
    else {
        var rest = ParametersWithEmpty2(ParmLists.Skip(1));
        foreach (var r in rest)
            yield return r; // empty.Concat(r)
        foreach (var p in ParmLists.First()) {
            foreach (var r in rest)
                yield return r.Prepend(p); // p.Concat(r)
        }
    }
}

【讨论】:

  • 感谢您的正确回答。不过,我是来学习的。我希望有更多关于如何将这样的东西放在一起的背景知识,这样我就可以自己解决这个问题。例如,我有一个(单独的)特殊情况,其中需要所有 3 个参数。我将如何修改它来做到这一点?
  • 嗯,这很棘手,但从概念上讲,@jdweng 的评论是正确的——你想在关卡中嵌套循环。考虑基本情况(只传入 f,g,h)以及应该返回什么,然后是下一个情况(使用 d,e 和 f,g,h)——应该返回什么?如果它只是(d和f,g)怎么办?最初的问题是没有空选择(假设每个级别在选择之前都有一个空)所以我模拟了基本情况是0rest 的额外yield 没有当前级别。我添加了代码以不包含空案例。
  • 我添加了一些 cmets 来尝试显示原始函数中的隐式空。
  • 我发现了一个错误。 ParamLists 可能为空,因此 ParamLists.First() 将失败。
  • 我假设您的意思是在Parameters 函数中?如果是这样,您只需将ParametersWithEmpty 的前两行添加到它:if (ParmLists.Count() == 0) yield break; 在函数的开头。
【解决方案2】:

尝试以下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        public static List<string[]> temp = new List<string[]>
        {
            // The order of these groups is important
            new string[] { "a", "b", "c" },
            new string[] { "d", "e" },
            new string[] { "f", "g", "h" }
        };
        static void Main(string[] args)
        {
            Recursive(0, new List<string>());
            Console.ReadLine();
        }
        public static void Recursive(int level, List<string> output)
        {
            if (level < temp.Count)
            {
                foreach (string value in temp[level])
                {
                    List<string> newList = new List<string>();
                    newList.AddRange(output);
                    newList.Add(value);
                    Console.WriteLine(string.Join(",", newList));
                    Recursive(level + 1, newList);
                }
            }
        }
    }
}

要获得从第 2 级和第 3 级开始的其他组合,请更改 main()

        static void Main(string[] args)
        {
            for (int i = 0; i < temp.Count; i++)
            {
                Recursive(i, new List<string>());
            }
            Console.ReadLine();
        }

【讨论】:

  • 谢谢。这是一个好的开始。但它只包括 3 个字母组合,不包括 2 或 1 个字母组合。
  • 我更改了代码。只需移动几行即可打印所有内容。
  • +1,尽管 NetMage 首先得到了答案。我确实需要做你在原始解决方案中所做的事情(总是需要一些参数),所以也为那个解决方案点赞。
  • 嗯...我刚试了一下,它仍然没有打印出所有有效的组合。对于单个字母组合,它在字母 c 处停止,并且不会更进一步。双打也缺少连击。
  • 它打印出所有组合,从“a,b,c”中提取一个,然后从“d,e”中提取一个,最后从“f,g,h”中提取一个。我在上面修改了 main 以获得额外的组合
【解决方案3】:

这是一个枚举所有组合的 Linq 表达式。您可以在online C# repl 中试用。这个想法是使用累加器“a”通过遍历临时集合“b”的每个项目来收集所有组合,并添加“b”的元素(单个项目组合)以及添加到迄今为止的每个累积组合。虽然很毛茸茸的表情。

var combinations = temp.Aggregate(Enumerable.Empty<IEnumerable<string>>(), (a, b) => a
          .Concat(b.Select(i => Enumerable.Repeat(i, 1)))
          .Concat(a.SelectMany(i => b.Select(j => i.Concat(Enumerable.Repeat(j, 1))))));

【讨论】:

  • 很好地使用了Aggregate。我将它转换为 Enumerable 而不是字符串数组,它仍然可以正常工作。
  • 哦,是的,更好的是,您不是在每个聚合步骤中创建和放弃集合,您只是链接迭代器(状态机)
猜你喜欢
  • 1970-01-01
  • 2016-03-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-27
  • 1970-01-01
  • 2020-09-21
  • 1970-01-01
相关资源
最近更新 更多