扩展 [可选]、分组和 |或文本中的运算符

发布于 2024-11-16 10:01:00 字数 1429 浏览 3 评论 0原文

我正在尝试扩展包含 [ ] 的句子以指示可选项,( ) 指示分组,以及 | 指示 or 运算符并枚举所有可能性。例如:

“嘿[那里]你[hood]。”应该返回四个句子:

Hey there you hood.
Hey there you.
Hey you hood.
Hey you.

最终目标如下:

输入:“(他|她)狗[非常|”非常]困惑。”

Output: His dog was very confused.
        His dog was extremely confused.
        His dog was confused.
        Her dog was very confused.
        Her dog was extremely confused.
        Her dog was confused.

我正在使用正则表达式匹配和递归来做到这一点。我在以下短语下搜索了 CPAN 和 SO:

扩展文本
扩展句子
扩展条件
扩展选项
扩大分组

但没有运气。

谢谢。


我编辑了这个问题,主要是为了更好地反映其演变,并删除了随着问题的演变而过时的大部分内容。上面的问题是下面大多数答案试图解决的问题。

我目前的状态如下:

在与上述问题搏斗一天后,我有两个非常接近我想要的解决方案。一张是我自己的,第二张是下面的 PLT。然而,我决定尝试一种完全不同的方法。

使用正则表达式并手动解析这些句子似乎是一种非常丑陋的做事方式。因此,我决定为我的“语言”编写语法,并使用解析器生成器为我解析它。

这给了我一个额外的抽象层,并避免了 Damian Conway 在 Perl 最佳实践中描述的以下场景:[关于正则表达式]

稍微剪切和粘贴并修改,哦,现在它根本不起作用,所以让我们修改一下,看看是否可以-那-帮助-不-它-没有-但是-我们现在承诺-所以-也许-如果-我们-改变-那-位-而不-嗯-那是-克洛Ser-但仍然不太对-也许-如果-我做了第三次重复-非贪婪-而不是-哎呀-现在-它-回到-根本不匹配--也许我应该将其发布到 PerlMonks.org 并看看他们是否知道出了什么问题

如果这些表达式的语法发生变化并且我稍后需要支持其他结构,它也会变得更加容易。


最后更新:

我使用开源工具包解决了我的问题。这将转录我的输入的 JSGF 版本并生成有限状态传感器。从那里您可以逐步完成 FST 以生成所有可能的结果。

I am trying to expand sentences that incorporate [ ] to indicate optionals, ( ) to indicate grouping, and | to indicate the or operator and enumerate all possibilities. So for example:

"Hey [there] you [hood]." should return four sentences:

Hey there you hood.
Hey there you.
Hey you hood.
Hey you.

The end goal would look like:

Input: "(His|Her) dog was [very|extremely] confused."

Output: His dog was very confused.
        His dog was extremely confused.
        His dog was confused.
        Her dog was very confused.
        Her dog was extremely confused.
        Her dog was confused.

I am doing it using regex matching and recursion. I have searched both CPAN and SO under the phrases:

Expanding text
expanding sentences
expanding conditionals
expanding optionals
expanding groupings

with no luck.

Thanks.


I have edited this question largely to better reflect its evolution and removed large portions which were made obsolete as the question evolved. The question above is the question that most of the answers below are attempting to address.

My current state is the following:

After wrestling with the problem above for a day I have two solutions very close to what I want. One is my own and the second is PLT's below. However, I have decided to try a fundamentally different approach.

Using regular expressions and manually parsing these sentences seems like a very ugly way of doing things. So I have decided to instead write a grammar for my "language" and use a parser-generator to parse it for me.

This gives me an additional layer of abstraction and avoids the following scenario described by Damian Conway in Perl Best Practices: [about regexps]

cut-and-paste-and-modify-slightly-and-oh-now-it-doesn't-work-at-all-so-let's-modify-it-some-more-and-see-if-that-helps-no-it-didn't-but-we're-commited-now-so-maybe-if-we-change-that-bit-instead-hmmmm-that's-closer-but-still-not-quite-right-maybe-if-I-made-that-third-repetition-non-greedy-instead-oops-now-it's-back-to-not-matching-at-all-perhaps-I-should-just-post-it-to-PerlMonks.org-and-see-if-they-know-what's-wrong

It also makes it much easier if the grammar of these expressions were to change and I needed to support other constructs later on.


Last update:

I solved my problem using an open source toolkit. This will transcribe a JSGF version of my input and generate a finite-state transducer. From there you can walk through the FST to generate all possible outcomes.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

云裳 2024-11-23 10:01:00

好的,另一个对答案的完整修订。这按预期工作。 :) 现在它还扩展了嵌套括号。换行符仍然是分隔符,但我添加了一种方法,可以在需要时将其快速更改为更复杂的内容。

基本上,我首先用括号 + 管道替换括号,因为 [word ](|word ) 是等效的。

然后,我提取了所有封装括号,例如 (you |myfriend)(you |my (|friend)friend)。然后,我将嵌套括号扩展为常规括号,例如 (you |my (|friend )friend ) 被替换为 (you |myfriend |myfriend)

完成后,可以使用原始子例程处理这些单词。

仍有待在更复杂的扩展上进行测试,但在我的测试过程中它运行良好。

这是修改后的代码:

use strict;
use warnings;

sub addwords {
    my ($aref, @words) = @_;
    my @total;
    for my $start (@$aref) {
        for my $add (@words) {
            push @total, $start . $add;
        }
    }
    return @total;
}

sub expand_words {
    my $str = shift;
    my @sentences = ('');
    for my $word (word_split($str)) {
        if ($word =~ /^([(])([^)]+)[)]$/) {
            my @options = split /\|/, $2;
            push @options, '' if ($1 eq '[');
            @sentences = addwords(\@sentences, @options);
        } else {
            @sentences = addwords(\@sentences, $word);
        }
    }
    return @sentences;
}

sub fix_parens {
    my $str = shift;
    $str =~ s/\[/(|/g;
    $str =~ s/\]/)/g;
    return $str;
}

sub fix_nested {
    my @array = @_;
    my @return;
    for (my $i=0; $i <= $#array; ) {
        my $inc = 1;
        my ($co, $cc);
        do {
            $co = () = $array[$i] =~ /\(/g;
            $cc = () = $array[$i] =~ /\)/g;
            if ( $co > $cc ) {
                $array[$i] .= $array[$i + $inc++];
            }
        } while ( $co > $cc );
        push @return, expand_nest($array[$i]);
        $i += $inc;
    }
    return @return;
}

sub expand_nest {
    my $str = shift;
    my $co = () = $str =~ /\(/g;
    return $str unless ($co > 1);
    while ($str =~ /([^|(]+\([^)]+\)[^|)]+)/) {
        my $match = $1;
        my @match = expand_words($match);
        my $line = join '|', @match;
        $match =~ s/([()|])/"\\" . $1/ge;
        $str =~ s/$match/$line/ or die $!;
    }
    return $str;
}

sub word_split {
    my $str = shift;
    my $delimeter = "\n";
    $str = fix_parens($str);
    $str =~ s/([[(])/$delimeter$1/g;
    $str =~ s/([])])/$1$delimeter/g;
    my @tot = split /$delimeter/, $str;
    @tot = fix_nested(@tot);
    return @tot;
}
my $str = "Hey [there ](you|my [friendly ]friend) where's my [red|blue]berry?";
my @sentences = expand_words($str);
print "$_\n" for (@sentences);
print scalar @sentences . " sentences\n";

将产生输出:

Hey you where's my berry?
Hey you where's my redberry?
Hey you where's my blueberry?
Hey my friend where's my berry?
Hey my friend where's my redberry?
Hey my friend where's my blueberry?
Hey my friendly friend where's my berry?
Hey my friendly friend where's my redberry?
Hey my friendly friend where's my blueberry?
Hey there you where's my berry?
Hey there you where's my redberry?
Hey there you where's my blueberry?
Hey there my friend where's my berry?
Hey there my friend where's my redberry?
Hey there my friend where's my blueberry?
Hey there my friendly friend where's my berry?
Hey there my friendly friend where's my redberry?
Hey there my friendly friend where's my blueberry?
18 sentences

Ok, another complete revision of the answer. This will work as intended. :) It now also expands nested parens. Newline is still the delimeter, but I added a way to quickly change it to something more complicated if the need arises.

Basically, I started with replacing brackets with parens + pipe, since [word ] and (|word ) are equivalent.

I then extracted all the encapsulating parens, e.g. both (you |my friend) and (you |my (|friendly ) friend ). I then expanded the nested parens into regular parens, e.g. (you |my (|friendly ) friend ) was replaced with (you |my friendly friend |my friend ).

With that done, the words could be processed with the original subroutine.

Remains to be tested on more complicated expansions, but it works fine during my testing.

Here's the revised code:

use strict;
use warnings;

sub addwords {
    my ($aref, @words) = @_;
    my @total;
    for my $start (@$aref) {
        for my $add (@words) {
            push @total, $start . $add;
        }
    }
    return @total;
}

sub expand_words {
    my $str = shift;
    my @sentences = ('');
    for my $word (word_split($str)) {
        if ($word =~ /^([(])([^)]+)[)]$/) {
            my @options = split /\|/, $2;
            push @options, '' if ($1 eq '[');
            @sentences = addwords(\@sentences, @options);
        } else {
            @sentences = addwords(\@sentences, $word);
        }
    }
    return @sentences;
}

sub fix_parens {
    my $str = shift;
    $str =~ s/\[/(|/g;
    $str =~ s/\]/)/g;
    return $str;
}

sub fix_nested {
    my @array = @_;
    my @return;
    for (my $i=0; $i <= $#array; ) {
        my $inc = 1;
        my ($co, $cc);
        do {
            $co = () = $array[$i] =~ /\(/g;
            $cc = () = $array[$i] =~ /\)/g;
            if ( $co > $cc ) {
                $array[$i] .= $array[$i + $inc++];
            }
        } while ( $co > $cc );
        push @return, expand_nest($array[$i]);
        $i += $inc;
    }
    return @return;
}

sub expand_nest {
    my $str = shift;
    my $co = () = $str =~ /\(/g;
    return $str unless ($co > 1);
    while ($str =~ /([^|(]+\([^)]+\)[^|)]+)/) {
        my $match = $1;
        my @match = expand_words($match);
        my $line = join '|', @match;
        $match =~ s/([()|])/"\\" . $1/ge;
        $str =~ s/$match/$line/ or die $!;
    }
    return $str;
}

sub word_split {
    my $str = shift;
    my $delimeter = "\n";
    $str = fix_parens($str);
    $str =~ s/([[(])/$delimeter$1/g;
    $str =~ s/([])])/$1$delimeter/g;
    my @tot = split /$delimeter/, $str;
    @tot = fix_nested(@tot);
    return @tot;
}
my $str = "Hey [there ](you|my [friendly ]friend) where's my [red|blue]berry?";
my @sentences = expand_words($str);
print "$_\n" for (@sentences);
print scalar @sentences . " sentences\n";

Will produce the output:

Hey you where's my berry?
Hey you where's my redberry?
Hey you where's my blueberry?
Hey my friend where's my berry?
Hey my friend where's my redberry?
Hey my friend where's my blueberry?
Hey my friendly friend where's my berry?
Hey my friendly friend where's my redberry?
Hey my friendly friend where's my blueberry?
Hey there you where's my berry?
Hey there you where's my redberry?
Hey there you where's my blueberry?
Hey there my friend where's my berry?
Hey there my friend where's my redberry?
Hey there my friend where's my blueberry?
Hey there my friendly friend where's my berry?
Hey there my friendly friend where's my redberry?
Hey there my friendly friend where's my blueberry?
18 sentences
a√萤火虫的光℡ 2024-11-23 10:01:00

Data::Generate。我在搜索组合时发现了这一点,这是您对单词集所做的数学术语。

Data::Generate. I found this while searching for combination which is the mathematical term of what you're doing with your sets of words there.

℉絮湮 2024-11-23 10:01:00

如果您由于语法和正则表达式语法之间的冲突而克服了一些丑陋的正则表达式,那么这里有一个相当简单的解决方案。它允许使用 [] 和 () 语法,实际上它们非常相似, [foo] 与 (foo| ) 相同。

其基础是用标记 #0、#1、#2... 替换每个交替,同时将它们存储在数组中。然后替换最后一个标记,生成几个短语,然后替换每个短语中的倒数第二个标记......直到所有标记都被替换。 高阶 Perl 的细心读者无疑会找到一种更优雅的方法来做到这一点。

#!/usr/bin/perl

use strict;
use warnings;

while( my $phrase=<DATA>)
  { my $original= $phrase;
    $phrase=~s{\[([^\]]*)\]}{($1| )}g;           # replace [c|d] by (c|d| )
    my $alts=[]; my $i=0;
    while( $phrase=~ s{\(([^)]*)\)}{#$i})        # replace (a|b) ... (c|d| )  by #0 ... #1
      { push @$alts, [ split /\|/, $1 ]; $i++;   # store [ ['a', 'b'], [ 'c', 'd', ' '] ]
      }

    my $expanded=[$phrase];                       # seed the expanded list with the phrase

    while( @$alts) { expand( $alts, $expanded); } # expand each alternation, until none left

    print "$original    - ", join( "    - ", @$expanded), "\n\n";

  }

exit;

# expand the last #i of the phrase in all the phrases in $expanded
sub expand
  { my( $alts, $expanded)=@_;
    my @these_alts= @{pop(@$alts)};   # the last alternations 
    my $i= @$alts;                    # the corresponding index in the phrases

    @$expanded= map { my $ph= $_;               
                     map { my $ph_e= $ph;       
                           $ph_e=~ s{#$i}{$_};  # replace the marker #i by one option
                           $ph_e=~ s{ +}{ };    # fix double spaces
                           $ph_e;
                         } @these_alts          # for all options
                   } @$expanded                 # for all phrases stored so far
  }


__DATA__
(His|Her) dog was [very|extremely

Here is a rather simple solution, if you get past some of the ugly regexps, due to collisions between your syntax and the regexp syntax. It allows for both the [] and the () syntax, which in fact are very similar, [foo] is the same as (foo| ).

The basis is to replace each alternation by a marker #0, #1, #2... while storing them in an array. then replace the last marker, generating several phrases, then replace the next-to last marker in each of those phrases... until all markers have been replaced. Attentive readers of Higher-order Perl will no doubt find a more elegant way to do this.

#!/usr/bin/perl

use strict;
use warnings;

while( my $phrase=<DATA>)
  { my $original= $phrase;
    $phrase=~s{\[([^\]]*)\]}{($1| )}g;           # replace [c|d] by (c|d| )
    my $alts=[]; my $i=0;
    while( $phrase=~ s{\(([^)]*)\)}{#$i})        # replace (a|b) ... (c|d| )  by #0 ... #1
      { push @$alts, [ split /\|/, $1 ]; $i++;   # store [ ['a', 'b'], [ 'c', 'd', ' '] ]
      }

    my $expanded=[$phrase];                       # seed the expanded list with the phrase

    while( @$alts) { expand( $alts, $expanded); } # expand each alternation, until none left

    print "$original    - ", join( "    - ", @$expanded), "\n\n";

  }

exit;

# expand the last #i of the phrase in all the phrases in $expanded
sub expand
  { my( $alts, $expanded)=@_;
    my @these_alts= @{pop(@$alts)};   # the last alternations 
    my $i= @$alts;                    # the corresponding index in the phrases

    @$expanded= map { my $ph= $_;               
                     map { my $ph_e= $ph;       
                           $ph_e=~ s{#$i}{$_};  # replace the marker #i by one option
                           $ph_e=~ s{ +}{ };    # fix double spaces
                           $ph_e;
                         } @these_alts          # for all options
                   } @$expanded                 # for all phrases stored so far
  }


__DATA__
(His|Her) dog was [very|extremely
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文