繁体   English   中英

如何根据括号中的范围获得每个可能的组合?

[英]How to get every possible combination base on the ranges in brackets?

寻找最好的方法来获取类似1[aC]3[1-6]07[R,EG]之类的东西并让它 output 一个看起来像下面的日志 - 基本上每个可能的组合都基于括号中的范围。

1a3107R
1a3107E
1a3107F
1a3107G
1b3107R
1b3107E
1b3107F
1b3107G
1c3107R
1c3107E
1c3107F
1c3107G

一直到1C3607G

抱歉,我对我要寻找的东西没有更多的技术性,只是不确定要解释的正确术语。

通常我们要获得所有组合的方法是将所有范围放入 arrays,然后使用嵌套循环遍历每个数组,并在内部循环中创建一个新项目,该项目将添加到我们的结果中。

但为了在这里做到这一点,我们首先需要编写一个可以解析范围字符串并返回由范围定义的char值列表的方法。 我在这里写了一个基本的,它适用于您的示例输入,但应该添加一些验证以确保输入字符串的格式正确:

public static List<char> GetRange(string input)
{
    input = input.Replace("[", "").Replace("]", "");
    var parts = input.Split(',');
    var range = new List<char>();

    foreach (var part in parts)
    {
        var ends = part.Split('-');

        if (ends.Length == 1)
        {
            range.Add(ends[0][0]);
        }
        else if (char.IsDigit(ends[0][0]))
        {
            var start = Convert.ToInt32(ends[0][0]);
            var end = Convert.ToInt32(ends[1][0]);
            var count = end - start + 1;
            range.AddRange(Enumerable.Range(start, count).Select(c => (char) c));
        }
        else
        {
            var start = (int) ends[0][0];
            var last = (int) ends[1][0];
            var end = last < start ? 'z' : last;

            range.AddRange(Enumerable.Range(start, end - start + 1)
                .Select(c => (char) c));

            if (last < start)
            {
                range.AddRange(Enumerable.Range('A', last - 'A' + 1)
                    .Select(c => (char) c));
            }
        }
    }

    return range;
}

现在我们可以从像"[aC]"这样的字符串中获取一系列值,我们需要一种方法来为每个范围创建嵌套循环,并根据输入字符串构建我们的值列表。

一种方法是用包含每个范围的占位符的字符串替换我们的输入字符串,然后我们可以为每个范围创建一个循环,并且在每次迭代中,我们可以用该范围中的字符替换该范围的占位符。

因此,我们将采用这样的输入: "1[aC]3[1-6]07[R,EG]" ,并将其转换为: "1{0}3{1}07{2}" 现在我们可以创建循环,从第一个范围中获取字符并为每个字符创建一个新字符串,将{0}替换为字符。 然后,对于这些字符串中的每一个,我们遍历第二个范围并创建一个新字符串,将{1}占位符替换为第二个范围中的一个字符,依此类推,直到我们为每个字符串都创建了新字符串可能的组合。

public static List<string> GetCombinatins(string input)
{
    // Sample input = "1[a-C]3[1-6]07[R,E-G]"
    var inputWithPlaceholders = string.Empty;   // This will become "1{0}3{1}07{2}"
    var placeholder = 0;
    var ranges = new List<List<char>>();

    for (int i = 0; i < input.Length; i++)
    {
        // We've found a range start, so replace this with our 
        // placeholder '{n}' and add the range to our list of ranges
        if (input[i] == '[')
        {
            inputWithPlaceholders += $"{{{placeholder++}}}";
            var rangeEndIndex = input.IndexOf("]", i);
            ranges.Add(GetRange(input.Substring(i, rangeEndIndex - i)));
            i = rangeEndIndex;
        }
        else
        {
            inputWithPlaceholders += input[i];
        }
    }

    if (ranges.Count == 0) return new List<string> {input};

    // Add strings for the first range
    var values = ranges.First().Select(chr =>
        inputWithPlaceholders.Replace("{0}", chr.ToString())).ToList();

    // Then continually add all combinations of other ranges
    for (int i = 1; i < ranges.Count; i++)
    {
        values = values.SelectMany(value =>
            ranges[i].Select(chr =>
                value.Replace($"{{{i}}}", chr.ToString()))).ToList();
    }

    return values;
}

现在有了这些方法,我们可以很容易地创建我们所有范围的 output:

static void Main()
{
    Console.WriteLine(string.Join(", ", GetCombinatins("1[a-C]3[1-6]07[R,E-G]")));

    GetKeyFromUser("\nPress any key to exit...");
}

Output

在此处输入图像描述

我将分三个阶段解决这个问题。 第一阶段是将源字符串转换为IEnumerable IEnumerable<string>的 IEnumerable。

static IEnumerable<IEnumerable<string>> ParseSourceToEnumerables(string source);

例如,源"1[AC]3[1-6]07[R,EG]"应转换为以下 6 个可枚举:

"1"
"A", "B", "C"
"3"
"1", "2", "3", "4", "5", "6"
"07"
"R", "E", "F", "G"

源中的每个文字都已转换为包含单个字符串的IEnumerable<string>

第二阶段是创建这些可枚举的笛卡尔积。

static IEnumerable<IEnumerable<T>> CartesianProduct<T>(
    IEnumerable<IEnumerable<T>> sequences)

最后(也是最简单的)阶段是将笛卡尔积的每个内部IEnumerable<string>连接到单个字符串。 例如序列"1", "A", "3", "1", "07", "R"到字符串"1A3107R"

最难的阶段是第一个阶段,因为它涉及到解析。 下面是部分实现:

static IEnumerable<IEnumerable<string>> ParseSourceToEnumerables(string source)
{
    var matches = Regex.Matches(source, @"\[(.*?)\]", RegexOptions.Singleline);
    int previousIndex = 0;
    foreach (Match match in matches)
    {
        var previousLiteral = source.Substring(
            previousIndex, match.Index - previousIndex);
        if (previousLiteral.Length > 0)
            yield return Enumerable.Repeat(previousLiteral, 1);
        yield return SinglePatternToEnumerable(match.Groups[1].Value);
        previousIndex = match.Index + match.Length;
    }
    var lastLiteral = source.Substring(previousIndex, source.Length - previousIndex);
    if (lastLiteral.Length > 0) yield return Enumerable.Repeat(lastLiteral, 1);
}

static IEnumerable<string> SinglePatternToEnumerable(string pattern)
{
    // TODO
    // Should transform the pattern "X,A-C,YZ"
    // to the sequence ["X", "A", "B", "C", "YZ"]
}

第二阶段也很难,但解决了。 我刚刚从Eric Lippert 的博客中获取了实现。

static IEnumerable<IEnumerable<T>> CartesianProduct<T>(
    IEnumerable<IEnumerable<T>> sequences)
{
    IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>() };
    return sequences.Aggregate(
        emptyProduct,
        (accumulator, sequence) =>
        accumulator.SelectMany(_ => sequence,
            (accseq, item) => accseq.Append(item)) // .NET Framework 4.7.1
    );
}

最后阶段只是调用String.Join

var source = "1[A-C]3[1-6]07[R,E-G]";
var enumerables = ParseSourceToEnumerables(source);
var combinations = CartesianProduct(enumerables);
foreach (var combination in combinations)
{
    Console.WriteLine($"Combination: {String.Join("", combination)}");
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM