如何使用正则表达式匹配CSV格式的字符串中的数字和数字范围?

6
通常情况下,我喜欢正则表达式的挑战,更好的是解决它们。
但似乎我有一个我无法解决的案例。
我有一串由分号分隔的值,就像CSV行一样,它可能看起来像这样:
123-234;FOO-456;45-67;FOO-FOO;890;FOO-123;11-22;123;123;44-55;098-567;890;123-FOO;
在此行中,我想匹配所有整数和整数范围,以便稍后提取它们。可能只有单个值(没有分号)。
经过大量搜索,我设法编写了以下表达式:
(?:^|;)(?\d+-\d+)(?:$|;)|(?:^|;)(?\d+)(?:$|;)
我正在使用的测试字符串:
  1. 123
  2. 123-234;FOO-456;45-67;FOO-FOO;890;FOO-123;11-22;123;123;44-55;098-567;890;123-FOO;
  3. 123-456
  4. 123-FOO
  5. FOO-123
  6. FOO-FOO

第1行和第3行匹配正确,第4、5、6行匹配不正确。
在第2行中,只有两个值中的一个被正确匹配。

这里有一个链接到regex101.com的示例: https://regex101.com/r/zA7uI9/5

我还需要将整数和范围分别选择(在不同的组中)。

注意:我找到了一个问题,它可以帮助我,尝试了它的答案(通过调整),但它没有起作用。
匹配数字和数字范围的正则表达式 你有什么想法,我漏掉了什么吗? 将“使用”此正则表达式的语言是C#,但我不知道这对我的问题是否有用。 由barlop添加 这是当前正则表达式给他的匹配项,如regex101.com链接所示
对于他的测试字符串 123-234;FOO-456;45-67;FOO-FOO;890;FOO-123;11-22;123;123;44-55;098-567;89
123-234
45-67
890
11-22
123
098-567

所以他的正则表达式似乎漏掉了其中一个123,还有44-45以及结尾处的89。


3
如果你有一个类似于 CSV 的字符串,为什么不使用 CSV 解析器呢? - Tomalak
尝试使用(?<=^|;)\d+(?:-\d+)?(?=$|;) - Wiktor Stribiżew
2
@barlop:这是一个组的名称:http://www.regular-expressions.info/named.html - Adassko
我会考虑在“;”上使用简单的字符串分割,然后逐个检查每一列。 - rrauenza
1
我又醒了。所以我的方法可行,但你只需要有命名组?好吧,这很容易:(?<=^|;)(?:(?<float>\d+-\d+)|(?<int>\d+))(?=$|;) - Wiktor Stribiżew
显示剩余11条评论
3个回答

6

C# CSV字符串解析

使用内置的CSV解析器并分别检查每个字段:

using Microsoft.VisualBasic.FileIO;
....
var str = "123-234;FOO-456;45-67;FOO-FOO;890;FOO-123;11-22;123;123;44-55;098-567;890;123-FOO;";
var csv_parser = new TextFieldParser(new StringReader(str));
csv_parser.HasFieldsEnclosedInQuotes = false;   // Fields are not enclosed with quotes
csv_parser.SetDelimiters(";");                  // Setting delimiter
string[] fields;
var range_fields = new List<string>();
var integer_fields = new List<string>();
while (!csv_parser.EndOfData)
{
    fields = csv_parser.ReadFields();
    foreach (var field in fields)
    {
        if (!string.IsNullOrWhiteSpace(field) && field.All(x => Char.IsDigit(x)))
        {
            integer_fields.Add(field);
            Console.WriteLine(string.Format("Intger field: {0}", field));
        }
        else if (!string.IsNullOrWhiteSpace(field) && Regex.IsMatch(field, @"\d+-\d+"))
        {
             range_fields.Add(field);
             Console.WriteLine(string.Format("Range field: {0}", field));
        }
    }
}
csv_parser.Close();

结果如下:
Range field: 123-234
Range field: 45-67
Intger field: 890
Range field: 11-22
Intger field: 123
Intger field: 123
Range field: 44-55
Range field: 098-567
Intger field: 890

修正正则表达式的方法

你的正则表达式失败的原因是你实际上使用非捕获组(即(?:^|;)(?:$|;))来匹配定界符,并将定界符“消耗掉”了。这意味着匹配的文本被附加到匹配值上,而且正则表达式索引会被移动到分号(或字符串开头/结尾)后面的位置。

你需要使用环视语法。它们不会消耗文本,只是检查当前位置之前或之后是否能找到与环视模式匹配的文本。因此,你有机会获取重叠匹配,这是环视非常方便的一种应用场景。

(?<=^|;)((?<range>\d+-\d+)|(?<integer>\d+))(?=$|;)

这是一个支持.NET正则表达式语法的.NET正则表达式演示

下面是一张示意图:

enter image description here

注意使用RegexOptions.ExplicitCapture标志:这样,我们就避免了使用带编号(即未命名)捕获组获取子匹配,并且只获得命名捕获(正是我们所需要的)。

C#演示

var s = "123-234;FOO-456;45-67;FOO-FOO;890;FOO-123;11-22;123;123;44-55;098-567;890;123-FOO;";
var rx = new Regex(@"(?<=^|;)((?<range>\d+-\d+)|(?<integer>\d+))(?=$|;)", RegexOptions.ExplicitCapture);
var result = rx.Matches(s)
        .Cast<Match>()
        .Select(x => x.Groups["range"].Success ? 
            x.Groups["range"].Value : x.Groups["integer"].Value
        ).ToList();
foreach (var x in result)
    Console.WriteLine(x);

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Niitaku
1
我已经添加了一个C#的CSV字符串解析代码,以展示如何解析CSV。不要忘记使用using Microsoft.VisualBasic.FileIO; - Wiktor Stribiżew
1
非常感谢您首先提出CSV解析器的方法。 - Tomalak

2

我在regex101中无法轻松地查看捕获组,所以这部分可能需要一些调整,但这可以正确获取所有匹配项,并进行捕获。希望有人能发布一个改进的答案,但在此期间。

(^\d+(?=;|$))|((?<=;)\d+$)|(?<=;)\d+(?=;)|\d+-\d+

由ro yo添加的图表如下:

正则表达式可视化

输入图片描述

逻辑是:

如果匹配 (^\d+(?=;|$)) 或者 ((?<=;)\d+$) 或者 (?<=;)\d+(?=;) 或者 \d+-\d+,那么就匹配成功。

例如,以123开头(或单独出现),以123结尾,中间出现123,或者任何范围内的数字都可以匹配成功。

我无法完全理解regex101.com列出的匹配结果,但是这个正则表达式是有效的。

C:\blah>echo 123-234;FOO-456;45-67;FOO-FOO;890;FOO-123;11-22;123;123;44-55;098-567;89| grep -oP "(^\d+(?=;))|((?<=;)\d+$)|(?<=;)\d+(?=;)|\d+-\d+"

123-234
45-67
890
11-22
123
123
44-55
098-567
89

1
我建议将表达式的第一部分从(^\d+(?=;))改为(^\d+(?=;|$)),这样如果不在列表中,就可以匹配单个整数。 - Ro Yo Mi
@barlop 感谢您提供详细的答案。这个表达式似乎正确地匹配了目标值。然而,我需要将整数与范围分开,以便在将结果连接到整数之前扩展范围。尽管如此,我非常感谢您花费时间解决我的问题。 - Niitaku
@Niitaku在这个网站上不需要感谢。虽然如果您发现有价值的内容,请为其点赞,这与感谢有些相似,但并非为了表扬努力,而是为了正确和有价值的回答或有价值的回复,尽管它可能无法解决问题。该网站的哲学之一是不说谢谢,因为当人们必须阅读它以寻找内容时,这会浪费他们的时间。 - barlop

1

说明

(?<=;|^)[0-9]+(?:-[0-9]+|(?=;|$))

Regular expression visualization

这个正则表达式将会做以下事情:
  • 匹配以分号分隔的数值
  • 从这些数值中提取单个整数,例如 123 或者一段整数范围,例如 123-456

示例

演示版

https://regex101.com/r/oL1cN2/2

示例文本

123
123-234;FOO-456;45-67;FOO-FOO;890;FOO-123;11-22;123;123;44-55;098-567;890;123-FOO;
123-456
123-FOO
FOO-123
FOO-FOO

样例匹配

123
123-234
45-67
890
11-22
123
123
44-55
098-567
890
123-456

你用什么生成了那张图片? - barlop
@barlop 哈哈哈!!! 我永远不会告诉你! 好的好的好的... 它是用 http://www.debuggex.com 创建的,该网站甚至还有一个“嵌入到stackoverflow”的选项。 - Ro Yo Mi
@RoYoMi 感谢您提供这个解释得非常清楚的答案。不幸的是,我还需要使用命名组来分离数字和范围匹配。但是非常感谢您为我的问题所做的研究和付出的时间。 - Niitaku
@Tomalak 不好意思,我想我没有看到问题,因为它在实时演示中似乎可以工作。也许你能为我解释一下? - Ro Yo Mi
匹配 098-56a。移动一个右括号。 - Tomalak
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接