到目前为止,所有答案都有一个根本缺陷;您正在要求一种算法,该算法将生成n 元素的随机组合,并且这种组合遵循一些逻辑规则,是否有效。如果不是,则应产生新的组合。显然,这种新组合应该是之前从未生产过的组合。所有提出的算法都没有强制执行这一点。例如,如果在1000000 可能的组合中,只有一个是有效的,那么您可能会浪费大量资源,直到生成该特定的唯一组合。
那么,如何解决这个问题?嗯,答案很简单,创建所有个可能的唯一解决方案,然后简单地以随机顺序生成它们。警告:我会假设输入流没有重复元素,如果有,那么某些组合将不是唯一的。
首先,让我们自己编写一个方便的不可变堆栈:
class ImmutableStack<T> : IEnumerable<T>
{
public static readonly ImmutableStack<T> Empty = new ImmutableStack<T>();
private readonly T head;
private readonly ImmutableStack<T> tail;
public int Count { get; }
private ImmutableStack()
{
Count = 0;
}
private ImmutableStack(T head, ImmutableStack<T> tail)
{
this.head = head;
this.tail = tail;
Count = tail.Count + 1;
}
public T Peek()
{
if (this == Empty)
throw new InvalidOperationException("Can not peek a empty stack.");
return head;
}
public ImmutableStack<T> Pop()
{
if (this == Empty)
throw new InvalidOperationException("Can not pop a empty stack.");
return tail;
}
public ImmutableStack<T> Push(T item) => new ImmutableStack<T>(item, this);
public IEnumerator<T> GetEnumerator()
{
var current = this;
while (current != Empty)
{
yield return current.head;
current = current.tail;
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
这将使我们的生活更轻松,同时通过递归生成所有组合。接下来,让我们正确获取我们的 main 方法的签名:
public static IEnumerable<IEnumerable<T>> GetAllPossibleCombinationsInRandomOrder<T>(
IEnumerable<T> data, int combinationLength)
好的,看起来差不多。现在让我们来实现这个东西:
var allCombinations = GetAllPossibleCombinations(data, combinationLength).ToArray();
var rnd = new Random();
var producedIndexes = new HashSet<int>();
while (producedIndexes.Count < allCombinations.Length)
{
while (true)
{
var index = rnd.Next(allCombinations.Length);
if (!producedIndexes.Contains(index))
{
producedIndexes.Add(index);
yield return allCombinations[index];
break;
}
}
}
好的,我们在这里所做的只是生成随机索引,检查我们尚未生成它(为此我们使用 HashSet<int>),然后返回该索引处的组合。
简单,现在我们只需要处理GetAllPossibleCombinations(data, combinationLength)。
这很简单,我们将使用递归。我们的救助条件是当我们当前的组合是指定的长度时。另一个警告:我在整个代码中省略了参数验证,应该注意检查null 或指定长度是否大于输入长度等。
只是为了好玩,我将在这里使用一些小的 C#7 语法:嵌套函数。
public static IEnumerable<IEnumerable<T>> GetAllPossibleCombinations<T>(
IEnumerable<T> stream, int length)
{
return getAllCombinations(stream, ImmutableStack<T>.Empty);
IEnumerable<IEnumerable<T>> getAllCombinations<T>(IEnumerable<T> currentData, ImmutableStack<T> combination)
{
if (combination.Count == length)
yield return combination;
foreach (var d in currentData)
{
var newCombination = combination.Push(d);
foreach (var c in getAllCombinations(currentData.Except(new[] { d }), newCombination))
{
yield return c;
}
}
}
}
现在我们可以使用这个了:
var data = "abc";
var random = GetAllPossibleCombinationsInRandomOrder(data, 2);
foreach (var r in random)
{
Console.WriteLine(string.Join("", r));
}
果然,输出是:
bc
cb
ab
ac
ba
ca