这是在大声呼喊递归解决方案。
首先,让我们创建一个方法,从给定的一组值生成一定长度的所有组合。因为我们只对生成字符串感兴趣,所以让我们利用string 是不可变的这一事实(参见 P.D.2);这使得递归函数更容易实现和推理:
static IEnumerable<string> GetAllCombinations<T>(
ISet<T> set, int length)
{
IEnumerable<string> getCombinations(string current)
{
if (current.Length == length)
{
yield return current;
}
else
{
foreach (var s in set)
{
foreach (var c in getCombinations(current + s))
{
yield return c;
}
}
}
}
return getCombinations(string.Empty);
}
仔细研究这种方法的工作原理。用手算出一些小例子来理解它。
现在,一旦我们知道如何生成所有可能的组合,构建字符串就很容易了:
- 找出指定字符串中通配符的数量:这将是我们的组合长度。
- 对于每个组合,将遇到通配符的每个字符按顺序插入到字符串中。
好的,让我们这样做:
public static IEnumerable<string> GenerateCombinations<T>(
this string s,
IEnumerable<T> set,
char wildcard)
{
var length = s.Count(c => c == wildcard);
var combinations = GetAllCombinations(set, length);
var builder = new StringBuilder();
foreach (var combination in combinations)
{
var index = 0;
foreach (var c in s)
{
if (c == wildcard)
{
builder.Append(combination[index]);
index += 1;
}
else
{
builder.Append(c);
}
}
yield return builder.ToString();
builder.Clear();
}
}
我们完成了。用法是:
var set = new HashSet<int>(new[] { 1, 2, 3, 4 });
Console.WriteLine(
string.Join("; ", "a@bc@d".GenerateCombinations(set, '@')));
果然,输出是:
a1bc1d; a1bc2d; a1bc3d; a1bc4d; a2bc1d; a2bc2d; a2bc3d;
a2bc4d; a3bc1d; a3bc2d; a3bc3d; a3bc4d; a4bc1d; a4bc2d;
a4bc3d; a4bc4d
这是最高效的实现吗?可能不是,但它的可读性和可维护性。除非您有未达到的特定性能目标,否则请编写有效且易于理解的代码。
P.D.我省略了所有错误处理和参数验证。
P.D.2:如果组合的长度很大,在GetAllCombinations 中连接字符串可能不是一个好主意。在这种情况下,我会让GetAllCombinations 返回一个IEnumerable<IEnumerable<T>>,实现一个简单的ImmutableStack<T>,并将其用作组合缓冲区而不是string。