【问题标题】:.NET Regex expression to parse list of numbers and number ranges.NET 正则表达式解析数字列表和数字范围
【发布时间】:2015-01-07 18:06:03
【问题描述】:

我需要解析一个逗号分隔的数字列表和数字范围。字符串由用户输入到 UI 中,看起来类似于以下之一(六个不同的示例输入):

1-3, 5, 7-10
1
21.1
1.2-3,5.1,7-10.1
1-3, 5.1, 7-10, 21
1.1-3.1,5.1,7.1-10.1

我的最终目标是收集可以稍后在下游处理的数字和数字范围。例如,在解析完上面的第一个字符串示例后,我的最终结果将是一个包含 3 个元素的集合:1-3、5 和 7-10。

使用 C# 和 .NET 正则表达式,这个模式很好地用我需要的项目填充 Matches 集合(注意使用非捕获组):

(\d+(?:\.\d+)?-\d+(?:\.\d+)?)|(\d+(?:\.\d+)?)

不过我有两个问题:

  1. 我的模式中是否需要所有这些,或者是否有更简短的模式?

  2. 当字符串中包含无效字符时,是否可以向模式添加一些内容以返回 0 个匹配项?例如,如果我在字符串中任何我不希望发生匹配的地方包含一个字母字符。现在我通过两遍执行此操作,一次验证字符串是否只有有效字符 [\d,.- ],另一遍用于获取匹配项,假设它在第一遍中验证。

提前感谢您的想法。

更新:

这是我最终采用的解决方案(请参阅@Xiaoy312 答案):

public static IEnumerable<DataRange> ParseInput(string input)
{
    if (!Regex.IsMatch(input.Replace(" ", string.Empty), @"^[\d\.,\-]+$"))
        return Enumerable.Empty<DataRange>();

    return Regex.Matches(input.Replace(" ", string.Empty), 
        @"(?<A>\d+(?:\.\d+)?)(?:-(?<B>\d+(?:\.\d+)?))?").Cast<Match>()
        .Select(m => new DataRange
        {
            A = double.Parse(m.Groups["A"].Value,
                 System.Globalization.CultureInfo.InvariantCulture),
            B = m.Groups["B"].Success ? double.Parse(m.Groups["B"].Value, 
                 System.Globalization.CultureInfo.InvariantCulture) : (double?)null
        });

}

public class DataRange
{
    public double A;
    public double? B;
}

以下是示例用法:

static void Main(string[] args)
{
    Console.WriteLine("A\tB");
    var items = ParseInput("1");
    Array.ForEach(items.ToArray(), i => Console.WriteLine("{0}\t{1}", i.A, i.B));
    items = ParseInput("21.1");
    Array.ForEach(items.ToArray(), i => Console.WriteLine("{0}\t{1}", i.A, i.B));
    items = ParseInput("1-3,5,7-10");
    Array.ForEach(items.ToArray(), i => Console.WriteLine("{0}\t{1}", i.A, i.B));
    items = ParseInput("1.2-3,5.1,7-10.1");
    Array.ForEach(items.ToArray(), i => Console.WriteLine("{0}\t{1}", i.A, i.B));
    items = ParseInput("1-3, 5.1,  7-10,21");
    Array.ForEach(items.ToArray(), i => Console.WriteLine("{0}\t{1}", i.A, i.B));
    items = ParseInput("1.1-3.1,5.1,7.1-10.1");
    Array.ForEach(items.ToArray(), i => Console.WriteLine("{0}\t{1}", i.A, i.B));
    items = ParseInput("1.1-3.1,5.1,7.1-10.1a");
    Array.ForEach(items.ToArray(), i => Console.WriteLine("{0}\t{1}", i.A, i.B));
}

样本输出:

A       B
1
21.1
1       3
5
7       10
1.2     3
5.1
7       10.1
1       3
5.1
7       10
21
1.1     3.1
5.1
7.1     10.1

【问题讨论】:

  • 你在做什么不能通过两个或三个String.Split() 调用来完成?
  • 类似于\d*(\.\d*)?(-(\d*(\.\d*)?))? 的东西应该是有效的简化。要验证整个内容,您必须将该正则表达式包装在另一个捕获组中,其中包含逗号和可选空格。
  • 我宁愿说\d+(?:\.\d+)?(?:-\d+(?:\.\d+)?)?。使用0 捕获组(整个匹配)。
  • 这么说,你可能最好只使用String.Splitdouble.TryParse
  • @valverij,没什么特别的。我对选择这条路线的正则表达式感到满意。

标签: c# .net regex


【解决方案1】:

第一次匆忙尝试:

public IEnumerable<object> ParseInput(string input)
{
    return Regex.Matches(input.Replace(" ", string.Empty), @"(?<A>\d+(\.\d+)?)(-(?<B>\d+(\.\d+)?))?").Cast<Match>()
        .Select(m => new
        { 
            A = m.Groups["A"].Value,  
            B = m.Groups["B"].Value
        });
}

固定:

public IEnumerable<DataRange> ParseInput(string input)
{
    if (!Regex.IsMatch(input.Replace(Environment.NewLine, string.Empty), @"^[\d\.,\- ]+$"))
        return Enumerable.Empty<object>();

    return input
        .Replace(" ", string.Empty)
        .Split(new[] { Environment.NewLine, "," }, StringSplitOptions.RemoveEmptyEntries)
        .Select(x => Regex.Match(x, @"(?<A>\d+(\.\d+)?)(-(?<B>\d+(\.\d+)?))?"))
        .Select(m => new DataRange
        {
            A = double.Parse(m.Groups["A"].Value, System.Globalization.CultureInfo.InvariantCulture),
            B = m.Groups["B"].Success ? double.Parse(m.Groups["B"].Value, System.Globalization.CultureInfo.InvariantCulture) : (double?)null
        });
}

public class DataRange
{
    public double A;
    public double? B;
}

输入:

    const string SampleInput = 
    @"1-3, 5, 7-10, 
1
21.1
1.2-3,5.1,7-10.1
1-3, 5.1, 7-10, 21
1.1-3.1,5.1,7.1-10.1";

输出:

A B
1 3 
5 null 
7 10 
1 null 
21,1 null 
1,2 3 
5,1 null 
7 10,1 
1 3 
5,1 null 
7 10 
21 null 
1,1 3,1 
5,1 null 
7,1 10,1 

【讨论】:

  • 谢谢!!!你的“第一次匆忙尝试”很多,它让我走了。然后我看到了您的“固定”示例,它超越了……谢谢。仅供参考,实际上不需要解析换行符,因为我的原始样本集中的每一行都是一个输入,即总共六个单独的输入,每个独立于另一个解析。
  • 哦,我还以为是单输入。哈哈。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多