查找已排序字符序列的所有组合

时间:2012-10-22 15:11:45

标签: c# combinations

对于以下字符a,b,c,d,我想找到以下组合。

序列始终排序。我想知道如何找到这些组合?

a
b
c
d

ab
ac
ad

bc
bd

cd

abc
abd
acd

bcd

abcd

5 个答案:

答案 0 :(得分:4)

你想要的是每一个组合。通常在获得组合时,您将获得特定大小的所有组合,n。我们首先创建该方法以从序列中获取大小为n的组合:

public static IEnumerable<IEnumerable<T>> Combinations<T>(
      this IEnumerable<T> source, int n)
{
    if (n == 0)
        yield return Enumerable.Empty<T>();


    int count = 1;
    foreach (T item in source)
    {
        foreach (var innerSequence in source.Skip(count).Combinations(n - 1))
        {
            yield return new T[] { item }.Concat(innerSequence);
        }
        count++;
    }
}

一旦你有了这个简单的问题就是将n的所有n的组合从1加到序列的大小:

public static IEnumerable<IEnumerable<T>> AllCombinations<T>(this IList<T> source)
{
    IEnumerable<IEnumerable<T>> output = Enumerable.Empty<IEnumerable<T>>();
    for (int i = 0; i < source.Count; i++)
    {
        output = output.Concat(source.Combinations(i));
    }
    return output;
}

使用它的一些示例代码:

var list = new List<string> { "a", "b", "c", "d" };

foreach (var sequence in list.AllCombinations())
{
    Console.WriteLine(string.Join(" ", sequence));
}

值得注意的是,对于除最微小的输入序列之外的所有操作,此操作都非常昂贵。它并不是最有效的,但即使你确实剔除了最后一点性能,你也无法计算超过15-20的序列组合,这取决于你愿意等待的时间长短。你的电脑有多好。

答案 1 :(得分:0)

您可以使用Combinatorics库为您计算(documentation),但正如Servy所说,数据的长度是需要多长时间的主要因素。

答案 2 :(得分:0)

它们不是真正的子集,因为没有什么可以阻止输入序列包含重复项,但以下扩展方法应该适用于一般情况:

public static IEnumerable<IEnumerable<T>> Subsets<T>(this IEnumerable<T> source)
{
    List<T[]> yielded = new List<T[]> { new T[0] };
    foreach(T t in source)
    {
        List<T[]> newlyYielded = new List<T[]>();
        foreach(var y in yielded)
        {
            var newSubset = y.Concat(new[] {t}).ToArray();
            newlyYielded.Add(newSubset);
            yield return newSubset;
        }
        yielded.AddRange(newlyYielded);
    }
}

基本上从空序列开始,它会添加空序列,并附加第一个项目。然后,对于这两个序列中的每一个,它将附加下一个项目的序列添加。那么对于这四个序列中的每一个......

这必须保留每个序列的副本,因此会占用大量内存。

要从字符串中获取字符串,可以将其称为

"abcd".Subsets().Select(chars => new string(chars.ToArray()))

如果你不会有很多角色,你可以利用你可以直接计算第n个子集的事实:

public static int SubsetCount(this string s)
{
    return 2 << s.Length;
}
public static string NthSubset(this string s, int n)
{
    var b = New StringBuilder();
    int i = 0;
    while (n > 0)
    {
        if ((n&1)==1) b.Append(s[i]);
        i++;
        n >>= 1;
    }
    return b.ToString();
}

答案 3 :(得分:0)

我编写了一个类来处理使用二项式系数的常用函数,这是您的问题所处的问题类型。我没有看过@Bobson建议的Cominatorics库,但我相信我的课程可能更快更高效。它执行以下任务:

  1. 以任意N选择K到文件的格式输出所有K索引。 K-index可以用更具描述性的字符串或字母代替。

  2. 将K索引转换为已排序二项系数表中条目的正确索引。这种技术比依赖迭代的旧发布技术快得多。它通过使用Pascal三角形中固有的数学属性来实现。我的论文谈到了这一点。我相信我是第一个发现和发布这种技术的人,但我可能错了。

  3. 将已排序的二项系数表中的索引转换为相应的K索引。我相信它可能比您找到的链接更快。

  4. 使用Mark Dominus方法计算二项式系数,这样就不太可能溢出并使用更大的数字。

  5. 该类是用.NET C#编写的,它提供了一种通过使用通用列表来管理与问题相关的对象(如果有)的方法。此类的构造函数采用名为InitTable的bool值,当为true时,将创建一个通用列表来保存要管理的对象。如果此值为false,则不会创建表。不需要创建表来执行上述4种方法。提供访问者方法来访问该表。

  6. 有一个关联的测试类,它显示了如何使用该类及其方法。它已经过2个案例的广泛测试,并且没有已知的错误。

  7. 要阅读此课程并下载代码,请参阅Tablizing The Binomial Coeffieicent

    您的问题的解决方案涉及为每个N选择K案例生成K索引。因此,在上面的示例中,N(A,B,C,D)有4种可能性,代码(在C#中)看起来像这样:

    int TotalNumberOfValuesInSet = 4;
    int N = TotalNumberOfValuesInSet;
    // Loop thru all the possible groups of combinations.
    for (int K = N - 1; K < N; K++)
    {
       // Create the bin coeff object required to get all
       // the combos for this N choose K combination.
       BinCoeff<int> BC = new BinCoeff<int>(N, K, false);
       int NumCombos = BinCoeff<int>.GetBinCoeff(N, K);
       int[] KIndexes = new int[K];
       // Loop thru all the combinations for this N choose K case.
       for (int Combo = 0; Combo < NumCombos; Combo++)
       {
          // Get the k-indexes for this combination, which in this case
          // are the indexes to each letter in the set starting with index zero.
          BC.GetKIndexes(Loop, KIndexes);
          // Do whatever processing that needs to be done with the indicies in KIndexes.
          ...
       }
       // Handle the final combination which in this case is ABCD since since K < N.
       ...
    }
    

答案 4 :(得分:0)

上面servy的代码非常优雅,但它不会生成与源代码长度相同的组合。

for (int i = 0; i < source.Count; i++) should be
for (int i = 0; i <= source.Count; i++).

以下是vb.net变体,它不能使用yield。

<Extension()>
Public Function Combinations(Of T)(source As IEnumerable(Of T), n As Integer) As IEnumerable(Of IEnumerable(Of T))
    Dim lstResults As New List(Of IEnumerable(Of T))

    If n = 0 Then
        lstResults.Add(Enumerable.Empty(Of T))
    Else
        Dim count As Integer = 1


        For Each item As T In source
            For Each innerSequence In source.Skip(count).Combinations(n - 1)
                lstResults.Add(New T() {item}.Concat(innerSequence))
            Next
            count += 1
        Next
    End If

    Return lstResults

End Function

<Extension()>
Public Function AllCombinations(Of T)(source As IList(Of T)) As IEnumerable(Of IEnumerable(Of T))
    Dim output As IEnumerable(Of IEnumerable(Of T)) = Enumerable.Empty(Of IEnumerable(Of T))()

    For i As Integer = 0 To source.Count
        output = output.Concat(source.Combinations(i))
    Next
    Return output

End Function