如何使用正则表达式捕获科学计数法中的负号?

8

我曾试图回答一个问题(后来被删除了),我认为这个问题是关于提取科学计数法文本表示的。 (使用R的正则表达式实现需要对元字符进行双重转义,可以在纯PCRE或Perl模式下使用,我并不真正理解它们之间的区别)。 我已经解决了大部分任务,但似乎仍然无法捕获捕获组中的前导负号。 我唯一成功的方法是使用前导括号:

> txt <- c("this is some random text (2.22222222e-200)", "other random (3.33333e4)", "yet a third(-1.33333e-40)", 'and a fourth w/o the "e" (2.22222222-200)')
> sub("^(.+\\()([-+]{0,1}[0-9][.][0-9]{1,16}[eE]*[-+]*[0-9]{0,3})(.+$)", "\\2" ,txt)
[1] "2.22222222e-200" "3.33333e4"       "-1.33333e-40"    "2.22222222-200" 

> sub("^(.+\\()([-+]?[0-9][.][0-9]{1,16}[eE]*[-+]*[0-9]{0,3})(.+$)", "\\2" ,txt)
[1] "2.22222222e-200" "3.33333e4"       "-1.33333e-40"    "2.22222222-200" 
 #but that seems to be "cheating" ... my failures follow:

> sub("^(.+)([-+]?[0-9][.][0-9]{1,16}[eE]*[-+]*[0-9]{0,3})(.+$)", "\\2" ,txt)
[1] "2.22222222e-200" "3.33333e4"       "1.33333e-40"     "2.22222222-200" 
> sub("^(.+)(-?[0-9][.][0-9]{1,16}[eE]*[-+]*[0-9]{0,3})(.+$)", "\\2" ,txt)
[1] "2.22222222e-200" "3.33333e4"       "1.33333e-40"     "2.22222222-200" 
> sub("^(.+)(-*[0-9][.][0-9]{1,16}[eE]*[-+]*[0-9]{0,3})(.+$)", "\\2" ,txt)
[1] "2.22222222e-200" "3.33333e4"       "1.33333e-40"     "2.22222222-200" 

我已经用诸如“科学计数法正则表达式减号”等术语耐心地在SO上搜索了很长时间。


你能否更新你的问题,清楚地展示起始输入和期望输出? - Tim Biegeleisen
1
@TimBiegeleisen 在最后三个中,"1.33333e-40"是不同的。 - akrun
谢谢...我现在明白了 ^ ^ - Tim Biegeleisen
使用str_extract_all函数,似乎可以通过unlist(str_extract_all(txt, '-?[0-9.]+e?[-+]?[0-9]*'))实现。 - akrun
可以将提取的数字与它们的 abs() 反身进行比较,并根据该比较来分离负数。 - baptiste
显示剩余4条评论
3个回答

6
你可以尝试。
 library(stringr)
 unlist(str_extract_all(txt, '-?[0-9.]+e?[-+]?[0-9]*'))
 #[1] "2.22222222e-200" "3.33333e4"       "-1.33333e-40"    "2.22222222-200" 

使用基于捕获前导括号的方法

 str_extract(txt, '(?<=\\()[^)]*')
 #[1] "2.22222222e-200" "3.33333e4"       "-1.33333e-40"    "2.22222222-200" 

我认为stringr现在使用了其他包中的代码,但我没有在软件包新闻中看到提及。 - IRTFM
@BondedDust 看起来是这样,我收到了“perl已弃用,请使用regexp”的消息。 - akrun
@BondedDust 根据 https://github.com/hadley/stringr ,它是建立在 stringi 之上的,后者使用 ICU 库提供快速、正确的常见字符串操作实现。 - akrun
这就是包,但我在我的安装版本中没有看到提到它的地方。也许这只存在于Github版本中?(尝试在Mac上编译时出现错误“configure: error: C++预处理器“/lib/cpp”未通过健全性检查”,所以我仍然使用CRAN版本。) - IRTFM
@BondedDust 我不确定那个。 - akrun

2

我认为第一个捕获组“(.+)”的“贪婪”特性是吞掉了第二个捕获组中可选的减号,因此我用否定字符类终止了第一个捕获组,现在已经成功了。这仍然显得笨拙,希望有更优雅的方法。在搜索过程中,看到了Python代码,似乎暗示存在“&real_number”正则表达式定义。

> sub("^(.+[^-+])([-+]?[0-9][.][0-9]{1,16}[eE]*[-+]*[0-9]{0,3})(.+$)", "\\2" ,txt,perl=TRUE)
[1] "2.22222222e-200" "3.33333e4"       "-1.33333e-40"    "2.22222222-200" 

在查看使用substr提取匹配项的str_extract_all代码后,我现在认为我应该选择gregexpr-regmatches范例来进行我的努力,而不是选择中间的三个捕获组策略。

> hits <- gregexpr('[-+]?[0-9][.][0-9]{1,16}[eE]*[-+]*[0-9]{0,3}', txt)
> ?regmatches
> regmatches(txt, hits)
[[1]]
[1] "2.22222222e-200"

[[2]]
[1] "3.33333e4"

[[3]]
[1] "-1.33333e-40"

[[4]]
[1] "2.22222222-200"

1

这似乎有效,并且不会匹配IP地址:

sub("^.*?([-+]?\\d+(?:\\.\\d*)*(?:[Ee]?[-+]?\\d+)?).*?$", "\\1", txt)
[1] "2.22222222e-200" "3.33333e4"       "-1.33333e-40"    "2.22222222-200"

奇怪的是,这并不是我最初使用的正则表达式。当第一个尝试失败时,我想回头在Perl中进行测试:

my @txt = (
  "this is some random text (2.22222222e-200)",
  "other random (3.33333e4)",
  "yet a third(-1.33333e-40)" ,
  'and a fourth w/o the "e" (2.22222222-200)');

map { s/^.*?[^-+]([-+]?\d+(?:\.\d*)*(?:[Ee]?[-+]?\d+)?).*?$/$1/ } @txt;

print join("\n", @txt),"\n";

而且看起来不错:

2.22222222e-200
3.33333e4
-1.33333e-40
2.22222222-200

所以同样的正则表达式在 R 中也可以使用,对吧?

sub("^.*?[^-+]([-+]?\\d+(?:\\.\\d*)*(?:[Ee]?[-+]?\\d+)?).*?$", "\\1", txt)
[1] "0" "4" "0" "0"

显然不是这样。我甚至通过在Javascript中尝试使用new RegExp("...")来确认双引号字符串的正确性,并且在那里它也可以正常工作。不确定R有什么不同之处,但是删除否定符号字符类就解决了问题。

R使用Ville Laurikari(http://laurikari.net/tre/)的TRE库版本进行非Perl正则表达式处理。 - IRTFM

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