生成所有槽数未知的组合
Posted
技术标签:
【中文标题】生成所有槽数未知的组合【英文标题】:Generate all combinations with unknown number of slots 【发布时间】:2018-06-06 02:22:13 【问题描述】:我有一个充满字符串的文本文件,每行一个。其中一些字符串将包含未知数量的“@”字符。每个“@”可以代表数字 1、2、3 或 4。我想为每个“@”生成所有可能的字符串组合(排列?)。如果每个字符串有一定数量的“@”,我只会使用嵌套的 for 循环(快速而肮脏)。我需要帮助找到一种更优雅的方式来处理未知数量的“@”。
示例1:输入字符串为a@bc
输出字符串为:
a1bc
a2bc
a3bc
a4bc
示例2:输入字符串为a@bc@d
输出字符串为:
a1bc1d
a1bc2d
a1bc3d
a1bc4d
a2bc1d
a2bc2d
a2bc3d
...
a4bc3d
a4bc4d
有人可以帮忙吗?我正在使用 C#。
【问题讨论】:
【参考方案1】:这实际上是一个递归函数的好地方。我不会写 C#,但我会创建一个函数 List<String> expand(String str)
,它接受一个字符串并返回一个包含扩展字符串的数组。
expand
然后可以搜索字符串以找到第一个 @
并创建一个包含字符串的第一部分 + 扩展的列表。然后,它可以在字符串的最后一部分调用expand
,并将其扩展中的每个元素添加到最后一部分扩展中的每个元素中。
使用 Java ArrayLists 的示例实现:
ArrayList<String> expand(String str)
/* Find the first "@" */
int i = str.indexOf("@");
ArrayList<String> expansion = new ArrayList<String>(4);
/* If the string doesn't have any "@" */
if(i < 0)
expansion.add(str);
return expansion;
/* New list to hold the result */
ArrayList<String> result = new ArrayList<String>();
/* Expand the "@" */
for(int j = 1; j <= 4; j++)
expansion.add(str.substring(0,i-1) + j);
/* Combine every expansion with every suffix expansion */
for(String a : expand(str.substring(i+1)))
for(String b : expansion)
result.add(b + a);
return result;
【讨论】:
【参考方案2】:我在这里为您提供解决手头问题的极简方法。 是的,就像其他人所说的那样,递归是要走的路。
递归非常适合这里,因为我们可以通过为输入的一小部分提供解决方案来解决这个问题,然后从另一部分重新开始,直到我们完成并合并结果。
每个递归都必须有一个停止条件——这意味着不再需要递归。
这里我的停止条件是字符串中不再有"@"
。
我使用字符串作为我的一组值 (1234
),因为它是一个 IEnumerable<char>
。
这里的所有其他解决方案都很棒,只是想向您展示一个简短的方法。
internal static IEnumerable<string> GetStrings(string input)
var values = "1234";
var permutations = new List<string>();
var index = input.IndexOf('@');
if (index == -1) return new [] input ;
for (int i = 0; i < values.Length; i++)
var newInput = input.Substring(0, index) + values[i] + input.Substring(index + 1);
permutations.AddRange(GetStrings(newInput));
return permutations;
使用 LINQ 的更短、更简洁的方法:
internal static IEnumerable<string> GetStrings(string input)
var values = "1234";
var index = input.IndexOf('@');
if (index == -1) return new [] input ;
return
values
.Select(ReplaceFirstWildCardWithValue)
.SelectMany(GetStrings);
string ReplaceFirstWildCardWithValue(char value) => input.Substring(0, index) + value + input.Substring(index + 1);
【讨论】:
【参考方案3】:这是在大声呼喊递归解决方案。
首先,让我们创建一个方法,从给定的一组值生成一定长度的所有组合。因为我们只对生成字符串感兴趣,所以让我们利用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
。
【讨论】:
以上是关于生成所有槽数未知的组合的主要内容,如果未能解决你的问题,请参考以下文章