生成所有槽数未知的组合

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&lt;char&gt;

这里的所有其他解决方案都很棒,只是想向您展示一个简短的方法。

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&lt;IEnumerable&lt;T&gt;&gt;,实现一个简单的ImmutableStack&lt;T&gt;,并将其用作组合缓冲区而不是string

【讨论】:

以上是关于生成所有槽数未知的组合的主要内容,如果未能解决你的问题,请参考以下文章

创建未知数量的循环

为啥redis集群的最大槽数是16384个?

CUDA - 如何返回未知大小的结果

Python - 所有组合,包括括号

Matlab 生成所有可能的团队组合

为字符串列表生成所有组合