在Java中使用正则表达式分割字符串提取数字

7
我想从像这样的字符串中提取数字:

我想从像这样的字符串中提取数字:

String numbers[] = "M0.286-3.099-0.44c-2.901,-0.436,,,123,0.123,.34".split(PATTERN);

我想从这个字符串中提取以下数字:

  • 0.286
  • -3.099
  • -0.44
  • -2.901
  • -0.436
  • 123
  • 0.123
  • .34

即:

  • 可能会有垃圾字符,例如 "M"、"c"、"c"
  • "-"符号是包括在数字中的,不是用来分割的
  • 一个“数字”可以是任何Float.parseFloat可以解析的内容,因此.34是有效的

我目前的进展如下:

String PATTERN = "([^\\d.-]+)|(?=-)";

这种方法在某种程度上有效,但显然还远非完美:

  • 无法跳过示例中的起始垃圾字符“M”
  • 无法处理连续的垃圾字符,例如中间的,,,

如何修复PATTERN以使其正常工作?


“通过使用正则表达式分割”是指必须使用 yourString.split(regex)while(matcher.find()){...} 也是可以接受的解决方案吗?在这种情况下,我反对使用split ,因为它可能会在结果数组的开头创建额外的空元素,就像在"notNumber123NotNumber".split(regexForNotNumber)中返回 ["", "123"]一样。 - Pshemo
6个回答

3
尝试这个正则表达式:(-?(?:\d+)?\.?\d+)
以下是示例:点此查看
非常感谢nhahtdh的评论。确实如此,我们可以进行以下更新:
[-+]?(?:\d+(?:\.\d*)?|\.\d+)

这里是更新后的演示

实际上,如果我们考虑所有可能的浮点输入字符串格式(例如:Infinity-Infinity000xffp23d88F),那么它可能会有点复杂。然而,我们仍然可以按照以下Java代码进行实现:

String sign = "[-+]?";
String hexFloat = "(?>0[xX](((\\p{XDigit}+)\\.?)|((\\p{XDigit}*)\\.(\\p{XDigit}+)))[pP]([-+])?(\\p{Digit}+)[fFdD]?)";
String nan = "(?>NaN)";
String inf = "(?>Infinity)";

String dig = "(?>\\d+(?:\\.\\d*)?|\\.\\d+)";
String exp = "(?:[eE][-+]?\\d+)?";
String suf = "[fFdD]?";
String digFloat = "(?>" + dig + exp + suf + ")";

String wholeFloat = sign + "(?>" + hexFloat + "|" + nan + "|" + inf + "|" + digFloat + ")";

String s = "M0.286-3.099-0.44c-2.901,-0.436,,,123,0.123d,.34d.34.34M24.NaNNaN,Infinity,-Infinity00,0xffp23d,88F";

Pattern floatPattern = Pattern.compile(wholeFloat);
Matcher matcher = floatPattern.matcher(s);
int i = 0;
while (matcher.find()) {
    String f =  matcher.group();
    System.out.println(i++ + " : " + f + " --- " +  Float.parseFloat(f) );
}  

接下来的输出如下所示:
0 : 0.286 --- 0.286
1 : -3.099 --- -3.099
2 : -0.44 --- -0.44
3 : -2.901 --- -2.901
4 : -0.436 --- -0.436
5 : 123 --- 123.0
6 : 0.123d --- 0.123
7 : .34d --- 0.34
8 : .34 --- 0.34
9 : .34 --- 0.34
10 : 24. --- 24.0
11 : NaN --- NaN
12 : NaN --- NaN
13 : Infinity --- Infinity
14 : -Infinity --- -Infinity
15 : 00 --- 0.0
16 : 0xffp23d --- 2.13909504E9
17 : 88F --- 88.0

1
你的正则表达式无法匹配 23.,而这是 parseFloat 的有效数字。 - nhahtdh
2
虽然你的解决方案没有问题,但匹配小数位可以使用 (?:\d+(?:\.\d*)?|\.\d+) - 它将匹配所有格式 23, 34., 34.2, .34. 而且,后缀似乎是 Java 的一件事,所以是否要解析它还有争议。 - nhahtdh
@nhahtdh 非常感谢!你说得对,你的解决方案更简洁适用于十进制数字。如果可能的话,请使用你的解决方案更新我的答案! - jawee

3
你可以使用这样的正则表达式:

/这里放你的正则表达式/

([-.]?\d+(?:\.\d+)?)

演示链接

图片描述

匹配信息:

MATCH 1
1.  [1-6]   `0.286`
MATCH 2
1.  [6-12]  `-3.099`
MATCH 3
1.  [12-17] `-0.44`
MATCH 4
1.  [18-24] `-2.901`
MATCH 5
1.  [25-31] `-0.436`
MATCH 6
1.  [34-37] `123`
MATCH 7
1.  [38-43] `0.123`
MATCH 8
1.  [44-47] `.34`

更新

Jawee的方法

正如Jawee在他的评论中指出的,对于.34.34存在问题,因此您可以使用他的正则表达式来解决这个问题。感谢Jawee指出这一点。

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

如果想要了解这个正则表达式背后的原理,可以查看此Debuggex图片:

正则表达式可视化

引擎解释:

1st Capturing group (-?(?:\d+)?\.?\d+)
   -? -> matches the character - literally zero and one time
   (?:\d+)? -> \d+ match a digit [0-9] one and unlimited times (using non capturing group)
   \.? matches the character . literally zero and one time
   \d+ match a digit [0-9] one and unlimited times

1
这个可以找到匹配项,但是OP想要分隔。 (考虑到OP知道前后查找,如果这正是他想要的模式,他本可以自己想出来) - aioobe
1
看起来如果测试字符串是“.34.34”,结果是一个匹配项为“.34.34”,而不是两个匹配项“.34”“.34”。另一个尝试可以是“(-?(?:\ d +)?.?\ d +)”。供参考。 - jawee
感谢 @jawee 指出这一点。我已经根据你的评论更新了答案。 - Federico Piazza
你的第二个无法匹配 23.,这是 parseFloat 的有效数字。 - nhahtdh
@nhahtdh,我理解你的观点,但并非所有情况都能涵盖。例如,如果你有3.123,你可以考虑三个值3..1233.123,因此我基于OP样本尽可能地覆盖了大多数场景。欢迎发表答案,以便OP可以看到其他方法。 - Federico Piazza
@Fede:jawee的修改答案应该可以处理所有这些情况。只要你把搜索顺序搞对了,它就永远不会出现部分匹配。(以你的例子3.123为例,只要你在\d+.\d+之前搜索\d+.和\d+,那么你总能找到3.123。除非没有前面的数字,否则.\d+的情况甚至不会被考虑。) - nhahtdh

2
您可以用一行代码实现它(但比aioobe的答案少了一步!):
String[] numbers = "M0.286-3.099-0.44c-2.901,-0.436,,,123,0.123,.34"
    .replaceAll("^[^.\\d-]+|[^.\\d-]+$", "") // remove junk from start/end
    .split("[^.\\d-]+"); // split on anything not part of a number

尽管调用次数较少,aioobe的答案更易于阅读和理解,这使得他的代码更好。

2
使用您自己创建的正则表达式,您可以按如下方式解决它:
String[] numbers = "M0.286-3.099-0.44c-2.901,-0.436,,,123,0.123,.34"
                          .replaceAll(PATTERN, " ")
                          .trim()
                          .split(" +");

另一方面,如果我是你,我会选择使用循环:
Matcher m = Pattern.compile("[.-]?\\d+(\\.\\d+)?").matcher(input);
List<String> matches = new ArrayList<>();
while (m.find())
    matches.add(m.group());

+1 我不同意:我认为你的一行代码是最好的,比我通常的做法更好。 - Bohemian
这个正则表达式将.34.34匹配为一个标记,这是不正确的。 - nhahtdh

1
我认为这正是你想要的:

String pattern = "[-+]?[0-9]*\\.?[0-9]+";
String line = "M0.286-3.099-0.44c-2.901,-0.436,,,123,0.123,.34";
Pattern r = Pattern.compile(pattern);
Matcher m = r.matcher(line);
List<String> numbers=new ArrayList<String>();

while(m.find()) {
    numbers.add(m.group());         
}

0

很好,你在这上面设置了一项赏金。
不幸的是,可能你已经知道,无法直接使用Java的字符串分割方法来完成此操作。

如果不能直接完成,那就没有必要把它弄成一个补丁,因为它实际上是一个补丁。

原因有很多,有些与此相关,有些则不相关。

首先,你需要定义一个良好的正则表达式作为基础。
这是我所知道的唯一一个能够验证和提取正确格式的正则表达式:

 # "((?=[+-]?\\d*\\.?\\d)[+-]?\\d*\\.?\\d*)"

 (                             # (1 start)
      (?= [+-]? \d* \.? \d )
      [+-]? \d* \.? \d* 
 )                             # (1 end)

所以,看着这个基本的正则表达式,很明显你想要它匹配那种形式。
在split的情况下,你想要它匹配这种形式,因为这就是你想要的位置
进行拆分。

当我查看Java的split时,我发现无论它匹配什么,都会被排除
在结果数组之外。

所以,假定使用split,第一件要匹配(并消耗)的是所有不是
这种形式的内容。这部分将是像这样的:

 (?:
      (?!
           (?= [+-]? \d* \.? \d )
           [+-]? \d* \.? \d* 
      )
      . 
 )+

由于剩下的唯一东西是有效的十进制数,下一个断点将会在有效数字之间。
这部分加上第一部分,将会是这样:

 (?:
      (?!
           (?= [+-]? \d* \.? \d )
           [+-]? \d* \.? \d* 
      )
      . 
 )+
 |         # or,
 (?<=
      (?= [+-]? \d* \.? \d )
      [+-]? \d* \.? \d* 
 )
 (?=
      (?= [+-]? \d* \.? \d )
      [+-]? \d* \.? \d* 
 )

突然间,我们遇到了一个问题...变长回顾断言
所以,整个事情就结束了。

最后不幸的是,Java(据我所知)没有提供将捕获组内容(在正则表达式中匹配)作为结果数组中的元素的方法。
Perl有这个功能,但我找不到Java中的这个能力。

如果Java有这个功能,断点子表达式就可以合并成无缝分割。
像这样:

 (?:
      (?!
           (?= [+-]? \d* \.? \d )
           [+-]? \d* \.? \d* 
      )
      . 
 )*
 (
      (?= [+-]? \d* \.? \d )
      [+-]? \d* \.? \d* 
 )

1
我喜欢你在这方面所付出的努力,但是解决缺乏条款的直接方法是将其分为两个步骤,这在这种情况下比你的Qtax技巧更有效。无论如何,谢谢你的努力。 - Unihedron
1
@Unihedro - 问题在于这不可能分成两个步骤。如果可以的话,两个步骤都必须包含数字有效性 (?=[+-]?\d*\.?\d)[+-]?\d*\.?\d*。最终,重复做同样的事情是毫无意义的。如果您认为有一个两步骤的过程,我很乐意驳斥它。 - user557597
你的正则表达式使用了可变长度的后顾断言,完全是错误的,因为它会在数字字符串“234235”中匹配空字符串(使用.NET测试工具)。 - nhahtdh
@nhahtdh - 我所知道的唯一支持将捕获缓冲区转换为元素的语言是Perl。无缝意味着非数字与捕获的数字并排过滤。没有其他结构。Perl总是在捕获元素之前插入一个空元素。它可以像这样被grep出来:@ary = grep { length } split/(?=[+-]?\d*\.?\d)([+-]?\d*\.?\d*)/, $teststr; - user557597
@nhahtdh - 是的,虚假的 (?<=)(?=) 断言不会起作用,因为没有办法在两个有效数字之间推进位置(除了沿着一起前进)。所以我猜我也应该提到这个。结果,又一个原因是不能使用 split 来完成它!! - user557597
显示剩余3条评论

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