【问题标题】:How can I reverse a string that contains combining characters in Perl?如何反转包含 Perl 中组合字符的字符串?
【发布时间】:2010-11-23 18:22:26
【问题描述】:

我有字符串"re\x{0301}sume\x{0301}"(打印如下:resume),我想将其反转为"e\x{0301}muse\x{0301}r"(émusér)。我不能使用 Perl 的 reverse,因为它将像 "\x{0301}" 这样的组合字符视为单独的字符,所以我最终得到 "\x{0301}emus\x{0301}er" (́emuśer)。如何反转字符串,但仍然尊重组合字符?

【问题讨论】:

    标签: perl unicode string reverse


    【解决方案1】:

    您可以使用\X special escape(匹配非组合字符和所有以下组合字符)和split 来制作字素列表(它们之间有空字符串),反转字素列表,然后join他们重归于好:

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    my $original = "re\x{0301}sume\x{0301}";
    my $wrong    = reverse $original;
    my $right    = join '', reverse split /(\X)/, $original;
    print "original: $original\n",
          "wrong:    $wrong\n",
          "right:    $right\n";
    

    【讨论】:

    • 对于那些对为什么字素之间有空字符串感到困惑的人(就像我一开始一样),这是因为split 是倒置的:它使用所需的数据作为分隔符。空字符串是两个字素“之间”的内容。只有在结果中包含分隔符,您才能将字形与“真实”结果混合在一起——一堆空字符串。避免这种情况的另一种(并且稍快)方法是使用m//g 来捕获字素:join '', reverse $original =~ /(\X)/g
    • 为了澄清迈克尔的评论,当您在正则表达式中使用内存括号时,您会触发“分隔符保留模式”。您可以取回您要拆分的部分之间的内容。但是,您不需要这样做。模式 (?=\X) 做同样的事情,没有额外的位。并不是说空字符串对于小字符串真的很重要。
    • 您指出“分隔符保留模式”是对的,谢谢,这很有帮助。但是, (?=\X) 不等价。为了证明,考虑这两个例子:split /(a)/, "abc" 不等价于 split /(?=a)/, "abc" 和 split /(b+c)/, "abbcd" 不等价拆分 /(?=b+c)/, "abbcd"
    • 确实,这些并不等同,但我没有使用它们。我只是在谈论我正在使用的特定东西。
    【解决方案2】:

    最好的答案是使用Unicode::GCStringas Sinan points out


    我稍微修改了Chas的例子:

    • 在 STDOUT 上设置编码以避免“打印中的宽字符”警告;
    • split 中使用积极的前瞻断言(并且没有分隔符保留模式)(显然在 5.10 之后不起作用,所以我将其删除)

    经过一些调整,基本上是一样的。

    use strict;
    use warnings;
    
    binmode STDOUT, ":utf8";
    
    my $original = "re\x{0301}sume\x{0301}";
    my $wrong    = reverse $original;
    my $right    = join '', reverse split /(\X)/, $original;
    
    print <<HERE;
    original: [$original]
       wrong: [$wrong]
       right: [$right]
    HERE
    

    【讨论】:

    • 哇。我喜欢 perl,但这种拆分表达式非常神奇。我的第一个想法是“蛮力”:创建一个函数来执行 split 所做的事情——返回一个字符串列表,其中的每个条目代表一个逻辑字符。然而你得到了那个列表(称之为@x),幸运的是,join('', reverse(@x)) 部分显然如下。
    • 神奇?为何如此?它只是一个没有副作用的正则表达式,它只做你所看到的。如果您认为这很神奇,那么您还没有看到 Perl 真正的黑魔法。你可以称之为聪明(虽然我不会),但它并不神奇。它可能只是你从未使用过的东西。
    • 我尝试使用 Perl v5.12.4 运行此示例,但没有成功。使用 /(\X)/ 代替了。出于兴趣,这个答案在以前的 Perl 版本中是否有效,还是我们忽略了显而易见的问题?
    • 看起来它在 5.10 下工作,但在 5.12 或 5.14 下不行。我认为这一定是一个新错误。
    • @briandfoy 我现在懒得看,你提交了关于这个的错误吗?
    【解决方案3】:

    你可以使用Unicode::GCString:

    Unicode::GCString 将 Unicode 字符串视为由 Unicode 标准附件 #29 [UAX #29] 定义的扩展字素簇序列。

    #!/usr/bin/env perl
    
    use utf8;
    use strict;
    use warnings;
    use feature 'say';
    use open qw(:std :utf8);
    
    use Unicode::GCString;
    
    my $x = "re\x{0301}sume\x{0301}";
    my $y = Unicode::GCString->new($x);
    my $wrong = reverse $x;
    my $correct = join '', reverse @{ $y->as_arrayref };
    
    say "$x -> $wrong";
    say "$y -> $correct";
    

    输出:

    简历 -> ́emuśer
    简历 -> émusér

    【讨论】:

      【解决方案4】:

      Perl6::Str-&gt;reverse 也可以。

      对于字符串résumé,还可以在reverseing之前使用Unicode::Normalize核心模块将字符串更改为完全组合形式(NFCNFKC);然而,这不是一个通用的解决方案,因为一些基本字符和修饰符的组合没有预先组合的 Unicode 代码点。

      【讨论】:

        【解决方案5】:

        其他一些答案包含效果不佳的元素。这是一个在 Perl 5.12 和 5.14 上测试的工作示例。未能指定 binmode 将导致输出生成错误消息。在 split 中使用肯定的前瞻断言(并且没有分隔符保留模式)将导致我的 Macbook 上的输出不正确。

        #!/usr/bin/perl
        
        use strict;
        use warnings;
        use feature 'unicode_strings';
        
        binmode STDOUT, ":utf8";
        
        my $original = "re\x{0301}sume\x{0301}";
        my $wrong    = reverse $original;
        my $right    = join '', reverse split /(\X)/, $original;
        print "original: $original\n",
              "wrong:    $wrong\n",
              "right:    $right\n";
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-04-02
          • 1970-01-01
          • 1970-01-01
          • 2021-01-16
          • 1970-01-01
          • 2015-10-01
          • 1970-01-01
          相关资源
          最近更新 更多