【问题标题】:extract substring from a string using regex in perl?在perl中使用正则表达式从字符串中提取子字符串?
【发布时间】:2017-03-26 08:10:09
【问题描述】:

尝试提取与字符串中的模式匹配的子字符串。 例如我有像下面这样的文字

[ Pierre/NNP Vinken/NNP ]
,/, 
[ 61/CD years/NNS ]
old/JJ ,/, will/MD join/VB 
[ the/DT board/NN ]
as/IN 
[ a/DT nonexecutive/JJ director/NN Nov./NNP 29/CD ]
./. 
[ Mr./NNP Vinken/NNP ]
is/VBZ 
[ chairman/NN ]
of/IN 

我想提取斜杠 (/) 之前的任何内容和斜杠之后的任何内容,但不知何故,我的正则表达式提取了第一个子字符串并忽略了该行中的其余子字符串。

我的输出如下所示:

tag:Pierre/NNP Vinken - word:Pierre/NNP Vinken/NNP ->1
tag:, - word:,/, ->1
tag:61/CD years - word:61/CD years/NNS ->1
tag:old/JJ ,/, will/MD join - word:old/JJ ,/, will/MD join/VB ->1
tag:the/DT board - word:the/DT board/NN ->1
tag:as - word:as/IN ->1
tag:a/DT nonexecutive/JJ director/NN Nov./NNP 29 - word:a/DT nonexecutive/JJ director/NN Nov./NNP 29/CD ->1
tag:. - word:./. ->1
tag:Mr./NNP Vinken - word:Mr./NNP Vinken/NNP ->1
tag:is - word:is/VBZ ->1
tag:chairman - word:chairman/NN ->1
tag:of - word:of/IN ->1

但我真正想要的是下面这样的东西

tag:NNP  - word:Pierre ->1
tag:NNP  - word:Vinken ->1
tag:,    - word:,      ->1
tag:CD   - word:61     ->1
.
.
etc.

我使用的代码:

    while (my $line = <$fh>) {
        chomp $line;
        #remove square brackets
        $line=~s/[\[\]]//;

        while($line =~m/((\s*(.*))\/((.*)\s+))/gi)
        {
            $word=$1;
            $tag=$2;
            #remove whitespace from left and right of string
            $word=~ s/^\s+|\s+$//g;
            $tag=~ s/^\s+|\s+$//g;
            $tags{$tag}++;
            $tagHash{$tag}{$word}++;
        }

    }
foreach my $str (sort keys %tagHash)
{
    foreach my $s (keys %{$tagHash{$str}} )
    {
        print "tags:$str - word: $s-> $tagHash{$str}{$s}\n";
    }
}

知道为什么我的正则表达式的行为不应该是这样的

编辑:

在我正在解析的文本文件中也有通配符和标点符号,这意味着文件将具有以下内容: ''/'' “/” ,/, ./. ?/? !/! . . . 等等

所以我想捕捉所有这些东西,而不仅仅是字母和数字字符。

【问题讨论】:

  • 那是因为 .* 的贪婪本性,你为什么不在 / 上进行拆分
  • 全部原因是因为当你点击这样的 [./.] 时,斜线左侧的任何内容都是单词,而右侧的任何内容都是该单词的标签,在这种情况下,两者都是将是相同的,如果我将它们存储到数组中可能会令人困惑,但我认为你是对的,因为在这种情况下,数组中的奇数位置将代表标签,偶数位置将代表单词。
  • 试试\b([\w\.]+?)\/([\w\.]+)\b@kero
  • 或:while( $line =~ m!([^/\s]+)/([^/\s]+)!g ) {
  • 试试this one\s*([\w\.,'"]+?)\/([\w\.,'"]+)\s*@kero

标签: regex perl


【解决方案1】:

我认为你有 tag/words,tagword 可能是一切,除了像 ],[,\s, 这样的一些字符:

\s*([^\[\]\s]+?)\/([^\[\]\s]+)\s*
    ^^^^^^^^^1

此正则表达式类似于您的原始模式。 (见DEMO

说明:

1- 此捕获组匹配不是[]\s 的每个字符.

【讨论】:

    【解决方案2】:

    围绕整个模式的最外面的一组括号被捕获到$1,这显然不是故意的。此外,.*\/ 的贪婪意味着它将所有内容都带到 last /。同样,.*\s+ 只留下最后一个空格。

    一种方法是使用否定字符类

    my ($word, $tag) = m{ ([^/\s]+) / ([^/\s]+) }x;
    

    模式[^/\s]+ 匹配一串一个或多个连续字符,每个字符都不是/ 或空格。所以你会在/ 前后得到一个“word”。如果您将“斜线之后的任何内容”作为文本所述,则不清楚下一个斜线之前应该是什么。

    你的方法可以这样

    while (my $line = <$fh>) 
    {
        while ( $line =~ m{ ([^/\s]+) / ([^/\s]+) }gx )
        {
            $tagHash{$2}{$1}++;
        }
    }
    

    其他计数似乎无关,因此我将其省略以专注于问题。


    但是,这里少了一点。

    当行与预期格式不同时,此方法无法检测到。例如

    word1/tag1 word2/tag2/tag3/word4/tag4

    悄悄地产生错误的结果。一些违规被跳过,但有很多不良案例。

    解决此问题的一种方法是对行进行预处理,检查所有斜线之间是否至少有两个单词,并且在 first 和 last 之后至少有一个。这意味着每一行都被处理了两次,而且它也变得更加混乱。例如

    while (my $line = <$fh>) 
    {
        my @parts = split '/', $line;
        if (not shift @parts or not pop @parts or grep { 2 > split } @parts) {
            warn "Unexpected format: $line";
            next;
        }
    
        $tagHash{$2}{$1}++  while $line =~ m{ ([^/\s]+) / ([^/\s]+) }gx;
    }
    

    这个检查改变了@parts 数组,所以如果以后需要那个数组,那么最好使用

    if (!$parts[0] or !$parts[-1] or grep { 2 > split } @parts[1..@parts-2])  { ...
    

    除了grep之外,还可以使用List::Util中的短路any

    另一种方法是改变方法,仔细解析行,而不是盲目地跳过正则表达式匹配。由于第一个和最后一个可能只有一个词,这可能很难用正则表达式来完成。只拆分和使用数组可能更清晰、更实用。

    很难想象一种格式总是匹配数据,所以我建议考虑其中一些。

    【讨论】:

      猜你喜欢
      • 2010-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-17
      • 2014-08-25
      • 2021-09-25
      • 1970-01-01
      相关资源
      最近更新 更多