对于以下字符a,b,c,d
,我想找到以下组合。
序列总是排序的。我想知道我应该如何找到组合?
一种 b C d 抗体 交流 广告 公元前 BD 光盘 美国广播公司 abd acd bcd A B C D
对于以下字符a,b,c,d
,我想找到以下组合。
序列总是排序的。我想知道我应该如何找到组合?
一种 b C d 抗体 交流 广告 公元前 BD 光盘 美国广播公司 abd acd bcd A B C D
你想要的是每一个组合。通常,在获取组合时,您会得到特定大小 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 的序列组合,这取决于你愿意等待多长时间并且你的电脑有多好。
您可以使用Combinatorics库为您计算它们(文档),但正如 Servy 所说,数据的长度是需要多长时间的主要因素。
它们不是真正的子集,因为没有什么可以阻止您的输入序列包含重复项,但以下扩展方法应该在一般情况下工作:
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();
}
我编写了一个类来处理处理二项式系数的常用函数,这是您的问题所属的问题类型。我没有看过@Bobson 建议的 Cominatorics 库,但我相信我的课程可能更快、更高效。它执行以下任务:
将任何 N 选择 K 的所有 K 索引以良好的格式输出到文件。K-indexes 可以替换为更具描述性的字符串或字母。
将 K 索引转换为已排序二项式系数表中条目的正确索引。这种技术比依赖迭代的旧已发布技术快得多。它通过使用帕斯卡三角形固有的数学属性来做到这一点。我的论文谈到了这一点。我相信我是第一个发现并发表这种技术的人,但我可能是错的。
将已排序二项式系数表中的索引转换为相应的 K 索引。我相信它可能比您找到的链接更快。
使用Mark Dominus方法计算二项式系数,它不太可能溢出并且适用于更大的数字。
该类是用 .NET C# 编写的,并提供了一种通过使用通用列表来管理与问题相关的对象(如果有)的方法。此类的构造函数采用一个名为 InitTable 的 bool 值,当它为 true 时,将创建一个通用列表来保存要管理的对象。如果此值为 false,则不会创建表。无需创建表即可执行上述 4 种方法。提供了访问器方法来访问表。
有一个关联的测试类,它显示了如何使用该类及其方法。它已经用 2 个案例进行了广泛的测试,并且没有已知的错误。
要了解此类并下载代码,请参阅制表二项式系数。
您的问题的解决方案涉及为每个 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.
...
}
上面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