生成包含未知插槽数的所有组合

时间:2017-12-24 05:34:00

标签: c# combinations

我有一个充满字符串的文本文件,每行一个。其中一些字符串将包含未知数量的" @"字符。每个" @"可以表示数字1,2,3或4.我想为每个" @" s生成所有可能的字符串组合(排列?)。如果每个字符串有一定数量的" @" s,我只使用嵌套for循环(快速和脏)。我需要帮助找到一种更优雅的方式来使用未知数量的" @" s。

示例1:输入字符串为a@bc

输出字符串为:

a1bc
a2bc
a3bc
a4bc

示例2:输入字符串为a@bc@d

输出字符串为:

a1bc1d
a1bc2d
a1bc3d
a1bc4d
a2bc1d
a2bc2d
a2bc3d
...
a4bc3d
a4bc4d

任何人都可以帮助这个吗?我正在使用C#。

4 个答案:

答案 0 :(得分:0)

这实际上是递归函数的一个相当好的地方。我不会编写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;
}

答案 1 :(得分:0)

我在控制台应用程序中测试了以下代码。看它是否符合您的要求。这里的主要思想是将给定的字符串分成几部分并组成所有可能的输出。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            //string value = "a@bc";
            string value = "a@bc@d";
            //string value = "a@bc@defghi@";

            var array = Generate(value);
            foreach (string str in array)
            {
                Console.WriteLine(str);
            }

            Console.ReadKey();
        }

        // This method will initiate generation of
        // all combinations for given value
        static string[] Generate(string value)
        {
            string[] parts = value.Split(new string[] { "@" }, StringSplitOptions.None);

            var count = (int)Math.Pow(4, parts.Length - 1);

            string[] array = new string[count];

            int index = 0;
            Generate(array, ref index, parts[0], 1, parts);

            return array;
        }

        // Here we generate all possible combinations
        // with recursion
        static void Generate(string[] array, ref int index, string value, int level, string[] parts)
        {
            bool flag = level + 1 < parts.Length;

            for (int i = 1; i < 5; i++)
            {
                string str = (value + i + parts[level]);

                if (flag)
                {
                    Generate(array, ref index, str, level + 1, parts);
                }
                else
                {
                    array[index] = str;
                    index++;
                }

            }
        }

    }
}

这是截图

enter image description here

答案 2 :(得分:0)

这是一个大声喊叫的递归解决方案。

首先,让我们创建一个方法,从给定的值集生成一定长度的所有组合。因为我们只对生成字符串感兴趣,所以我们可以利用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);
}

仔细研究这种方法的工作原理。手工完成小样例来理解它。

现在,一旦我们知道如何生成所有可能的组合,构建字符串很容易:

  1. 计算出指定字符串中的通配符数量:这将是我们的组合长度。
  2. 对于每个组合,请按顺序将每个字符插入到我们遇到通配符的字符串中。
  3. 好的,我们就这样做:

    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
    

    这是最高效或最有效的实施方案吗?可能不是,但它的可读性和可维护性。除非您有特定的性能目标,否则请编写有效且易于理解的代码。

    Pd积。我省略了所有错误处理和参数验证。

    P.D.2:如果组合的长度很大,GetAllCombinations内的串联字符串可能不是一个好主意。在这种情况下,我会GetAllCombinations返回IEnumerable<IEnumerable<T>>,实现一个简单的ImmutableStack<T>,并将其用作组合缓冲区而不是string

答案 3 :(得分:0)

我在这里为您提供一个极简主义的解决方案。 是的,像其他人一样说递归就是去这里的方式。

递归是一个完美的选择,因为我们可以通过为输入的一小部分提供解决方案来解决这个问题,并在另一部分重新开始直到我们完成并合并结果。

每次递归都必须有一个停止条件 - 意味着不再需要递归。

这里我的停止条件是字符串中不再有"@"。 我使用字符串作为我的一组值(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);
}