从已截断为不同长度的字符串末尾删除单词

时间:2018-12-23 22:23:25

标签: regex perl

所以说我有一个字符串列表,这些字符串有时以被截短为不同长度的短语结尾。在此示例中,短语为“ hello”。

my @strings =
(
    "Test 1 hello",
    "Something else",
    "Test 2 hell",
    "And also he",
    "Test 4 hel"
);

这是我现在要删除“ hello”片段的方式:

foreach my $string (@strings)
{
    if ($string =~ m/(.*?)\s*(h(e(l(lo?)?)?)?)?$/)
    {
        print "'", $string, "' -> '", $1, "'\n";
    }
}

它确实起作用:

'Test 1 hello' -> 'Test 1'
'Something else' -> 'Something else'
'Test 2 hell' -> 'Test 2'
'And also he' -> 'And also'
'Test 4 hel' -> 'Test 4'

但是,我发现正则表达式可以匹配所有“ hello”片段,它们长,混乱且难以修改,以备将来使用。 有没有比(h(e(l(lo?)?)?)?)?$等效的写法?

5 个答案:

答案 0 :(得分:2)

一种构建正则表达式的方法是替换可能的字符串版本。我认为这也应该扩展到更广泛的用途

use warnings;
use strict;
use feature 'say';

my $target = shift || 'hello';

my @strings = (
    "Test 1 hello",
    "Something else",
    "Test 2 hell",
    "And also he",
    "Test 4 hel"
);

my $re_versions = build_regex($target);

foreach my $string (@strings)
{
    if ($string =~ /($re_versions)$/)
    {
        say "'$string' --> $1";
    }
};

sub build_regex {
    my ($s) = @_;
    my @versions;
    while ($s) {
        push @versions, quotemeta $s;
        chop $s;
    }
    return join '|', @versions;
}

这并不短(虽然可以肯定地写出了 ),但是对于可接受的字符串版本,匹配顺序等的细化,应该可以管理。

如果有理由要返回已编译的正则表达式,请将函数返回至

my $re_str = join '|', @versions;
return qr/$re_str/;

现在您还可以在其中添加可能合适的标志。

答案 1 :(得分:1)

您正在寻找一个正则表达式以匹配字符串末尾的以下表达式 hellohellhelheh。我们可以期望该表达式的前面至少有一个空格。

您可以写:

s/\s+(hello$)|(hell$)|(hel$)|(he$)|(h$)// for @strings;

这会将数组中的所有元素适当地修改为期望的值。

我需要,您可以为任何给定的单词自动生成匹配字符串:

my $word  = "hello";
my @parts = map { substr $word, 0, $_ } (1..(length $word));
my $match = join "|", map { "(" . $_ . "\$)" } @words;
s/\s+$match// for @strings;

答案 2 :(得分:1)

dawg的答案简化了正则表达式,但不适用于比我的示例更复杂的用例。 GMB的答案在任何情况下都有效,但是也会导致冗长(但公认地更好理解)正则表达式。我个人的解决方案是使用一个函数从任何需要的字符串动态构造正确的正则表达式:

#!/usr/bin/perl

use strict;
use warnings;

my @strings =
    (
        "Test 1 hello",
        "Something else",
        "Test 2 hell",
        "And also he",
        "Test 4 hel"
    );

my $regex = cutOffStringRegex('hello');

foreach my $string (@strings)
{
    if ($string =~ m/(.*?)\s*$regex$/x)
    {
        print "'", $string, "' -> '", $1, "'\n";
    }
}

sub cutOffStringRegex
{
    my ($string) = @_;
    my $resultString = "";
    if (length($string) == 1)
    {
        $resultString = quotemeta $string;
    }
    else
    {
        my $firstChar = quotemeta(substr $string, 0, 1);
        my $rest = substr $string, 1;
        $resultString = $firstChar . cutOffStringRegex($rest);
    }
    return '(' . $resultString . ')?';
}

cutOffStringRegex('hello')产生"(h(e(l(l(o)?)?)?)?)?"。 由于我的问题是“如何写得这么短”,因此我不会将此答案标记为正确的答案,因为它肯定不会更短。

答案 3 :(得分:1)

您可以使用相反的逻辑:而不是搜索 部分hello,抓住最后一个单词并在搜索 hello

也许它不会更短一些,但是可以更干净。 使用/(\w+)$/来抓住最后一个单词就很容易了,并且 检查它是否包含在hello中,不需要正则表达式。一种 只需调用index即可。

foreach (@strings) {
    (my $original = $_) =~ /(\w+)$/;
    s/\s*\w+$// unless index('hello', $1);
    say "'$original' -> '$_'";
}

为清楚起见,index返回子字符串的索引 $1中的'hello'。我们只在乎这种情况 返回0,表示它存在并且在开始(将 如果不存在或大于0,则为-1 位置)。这就是为什么我们仅在 unless的运算为0。

答案 4 :(得分:0)

如果要删除以he开头的片​​段,该片段为可选片段:

#!/usr/bin/perl

use 5.020;
use strict;
use warnings;

my @strings =
(
    "Test 1 hello",
    "Something else",
    "Test 2 hell",
    "And also he",
    "Test 4 hel"
);

for (@strings){
    s/\hhe[lo]*$//;
    say;
}

打印:

Test 1
Something else
Test 2
And also
Test 4

或者,您可以匹配并保留您想要的东西:

for (@strings){
    say $1 if /^(.*?)(?:\hhe[lo]*)?$/;
}
# same output

如果要确保捕获的文本按此顺序与字符hello匹配,请匹配捕获的子字符串:

for (@strings){
    say if /^(.*?)( he[lo]*)?$/ && (!$2 || ' hello' =~ /^$2/);
    }