【问题标题】:Better regular expression for ReverseStringFormat更好的 ReverseStringFormat 正则表达式
【发布时间】:2015-02-20 05:57:54
【问题描述】:

我已经使用了一段时间了,这个简洁的功能在 SO 上找到了 here

    private List<string> ReverseStringFormat(string template, string str)
    {
        string pattern = "^" + Regex.Replace(template, @"\{[0-9]+\}", "(.*?)") + "$";
        Regex r = new Regex(pattern);
        Match m = r.Match(str);

        List<string> ret = new List<string>();
        for (int i = 1; i < m.Groups.Count; i++)
            ret.Add(m.Groups[i].Value);

        return ret;
    }

此函数能够正确处理模板,例如:

My name is {0} and I'm {1} years old

虽然它因以下模式而失败:

My name is {0} and I'm {1:00} years old

我想处理这种失败的情况并添加固定长度解析。 该函数将(第一个)模板转换如下:

My name is (.*?) and I'm (.*?) years old

我一直在尝试编写上述正则表达式来限制为第二组捕获的字符数,但没有成功。这是我的(可怕的)尝试:

My name is (.*?) and I'm (.{2}) years old

我一直在尝试处理如下输入,但以下 PATTERN 不起作用:

PATTERN: My name is (.*?) (.{3})(.{5})
INPUT: My name is John 123ABCDE
EXPECTED OUTPUT: John, 123, ABCDE

每一个建议都非常感谢

【问题讨论】:

  • 您的输入和预期输出是什么?
  • 重新排序实际参数而不是字符串格式本身不是更容易吗?
  • 我一直在使用这个函数进行可配置的解析。如果可能的话,我想扩展它......也许我不明白你对重新排序实际参数的意思
  • @juharr 是对的。关于模板,我们唯一知道的是什么已经可以工作,{1:00} 不工作,但实际上,{1:00} 没有任何意义。
  • @dna2 这很奇怪,因为 (.{3}) 应该可以工作...尝试在regex101.com上进行测试

标签: c# regex


【解决方案1】:

您不太可能在同一个正则表达式替换中测量捕获组的长度。

我强烈建议您查看以下状态机实现。 请注意,此实现还解决了string.Format 的多个花括号转义功能。

首先你需要一个状态枚举,非常像这个:

public enum State {
    Outside,
    OutsideAfterCurly,
    Inside,
    InsideAfterColon
}

然后您将需要一种很好的方法来遍历字符串中的每个字符。 string chars 参数代表您的 template 参数,而返回的 IEnumerable&lt;string&gt; 代表结果模式的连续部分:

public static IEnumerable<string> InnerTransmogrify(string chars) {
    State state = State.Outside;
    int counter = 0;

    foreach (var @char in chars) {
        switch (state) {
            case State.Outside:
                switch (@char) {
                    case '{':
                        state = State.OutsideAfterCurly;
                        break;
                    default:
                        yield return @char.ToString();
                        break;
                }
                break;
            case State.OutsideAfterCurly:
                switch (@char) {
                    case '{':
                        state = State.Outside;
                        break;
                    default:
                        state = State.Inside;
                        counter = 0;
                        yield return "(.";
                        break;
                }
                break;
            case State.Inside:
                switch (@char) {
                    case '}':
                        state = State.Outside;
                        yield return "*?)";
                        break;
                    case ':':
                        state = State.InsideAfterColon;
                        break;
                    default:
                        break;
                }
                break;
            case State.InsideAfterColon:
                switch (@char) {
                    case '}':
                        state = State.Outside;
                        yield return "{" + counter + "})";
                        break;
                    default:
                        counter++;
                        break;
                }
                break;
        }

    }

}

你可以像这样加入这些部分:

public static string Transmogrify(string chars) {
    var parts = InnerTransmogrify(chars);
    var result = string.Join("", parts);
    return result;
}

然后像你最初打算的那样把所有东西都包起来:

private List<string> ReverseStringFormat(string template, string str) {
    string pattern = <<SOME_PLACE>> .Transmogrify(template);
    Regex r = new Regex(pattern);
    Match m = r.Match(str);

    List<string> ret = new List<string>();
    for (int i = 1; i < m.Groups.Count; i++)
        ret.Add(m.Groups[i].Value);

    return ret;
}

希望您了解为什么 Regex 语言对于此类工作的表达力不够(至少就我的理解而言)。

【讨论】:

  • 你是英雄!很难承认我只理解你答案的 50%,但我会研究它!谢谢:)
  • 您可以阅读有关实现状态机的信息。基本上,我们正在做的是区分 4 种情况。这些情况的名称是不言自明的(查看enum 的字段)。基本上Outside 意味着我们在附近的某个地方“>>我的名字是Inside 时,这意味着我们在“{>>2:000
  • @EduardDumitru 实际上,带有平衡组(或堆栈)的正则表达式具有足够的表现力,而 .NET 实现具有这些。见:regular-expressions.info/balancing.html
  • 我一直在调试它,它很棒:) 我只发现你忘记了 case State.Inside 包括 case ":" 并将状态更改为 InsideAfterColumn... 之后是完美的!非常感谢!!并感谢简洁的代码......我一直在使用状态机,但从未使用过字符!这是状态机的一个很好的用法!
  • 嘿,从 Visual Studio 复制粘贴了错误的代码 :)。你说的对。我想,这样你就可以多消化一点代码了。我编辑了我的答案以解决冒号问题。
【解决方案2】:

解决正则表达式问题的唯一方法是使用自定义匹配器来替换组捕获长度。

下面的代码在您的示例中执行此操作:

private static string PatternFromStringFormat(string template)
{
    // replaces only elements like {0}
    string firstPass = Regex.Replace(template, @"\{[0-9]+\}", "(.*?)");
    // replaces elements like {0:000} using a custom matcher
    string secondPass = Regex.Replace(firstPass, @"\{[0-9]+\:(?<len>[0-9]+)\}",
        (match) =>
        {
            var len = match.Groups["len"].Value.Length;
            return "(.{" + len + "*})";
        });

    return "^" + secondPass + "$";
}

private static List<string> ReverseStringFormat(string template, string str)
{
    string pattern = PatternFromStringFormat(template);

    Regex r = new Regex(pattern);
    Match m = r.Match(str);

    List<string> ret = new List<string>();
    for (int i = 1; i < m.Groups.Count; i++)
        ret.Add(m.Groups[i].Value);

    return ret;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 2011-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-02
    相关资源
    最近更新 更多