【问题标题】:Split string at given positions在给定位置拆分字符串
【发布时间】:2020-01-22 17:06:46
【问题描述】:

我如何在位置列表中很好地/惯用地拆分字符串?

我有什么:

.say for split-at( "0019ABX26002", (3, 4, 8) ); 

sub split-at( $s, @positions )
{
  my $done = 0;

  gather 
  {
    for @positions -> $p
    {
      take $s.substr($done, $p - $done );
      $done = $p;
    }
    take $s.substr( $done, * );
  }
}

这是合理的。不过,我对此缺乏语言支持感到困惑。如果“拆分”是一件事,为什么“拆分”也不行?我认为这应该是一个核心操作。我应该会写

.say for "0019ABX26002".split( :at(3, 4, 8) );

或者我忽略了什么?

编辑:我们目前所拥有的一个小基准

O------------O---------O------------O--------O-------O-------O
|            | Rate    | array-push | holli  | raiph | simon |
O============O=========O============O========O=======O=======O
| array-push | 15907/s | --         | -59%   | -100% | -91%  |
| holli      | 9858/s  | 142%       | --     | -100% | -79%  |
| raiph      | 72.8/s  | 50185%     | 20720% | --    | 4335% |
| simon      | 2901/s  | 1034%      | 369%   | -98%  | --    |
O------------O---------O------------O--------O-------O-------O

代码:

use Bench;

my $s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccddddddddddddddddddddddddddddddddddddefggggggggggggggggggg";
my @p = 29, 65, 69, 105, 106, 107;

Bench.new.cmpthese(1000, {
  holli  => sub { my @ = holli($s, @p); },
  simon => sub { my @ = simon($s, @p); },
  raiph => sub { my @ = raiph($s, @p); },
  array-push => sub { my @ = array-push($s, @p); },
});

#say user($s, @p);


sub simon($str, *@idxs ) {
    my @rotors = @idxs.map( { state $l = 0; my $o = $_ - $l; $l = $_; $o } );
    $str.comb("").rotor( |@rotors,* ).map(*.join(""));
}

sub raiph($s, @p) {
    $s.split( / <?{$/.pos == any(@p)}> / )
}

sub holli( $s, @positions )
{
  my $done = 0;

  gather
  {
    for @positions -> $p
    {
      take $s.substr($done, $p - $done );
      $done = $p;
    }
    take $s.substr( $done, * );
  }
}

sub array-push( $s, @positions )
{
  my $done = 0;
  my @result;

  for @positions -> $p
  {
    @result.push: $s.substr($done, $p - $done );
    $done = $p;
  }
  @result.push: $s.substr( $done, * );

  @result;
}

【问题讨论】:

  • 所以你期待这个:("001", "9", "ABX2", "6002")?
  • 在这种情况下,这将是输出 yes。
  • 如果您正在寻找原始速度,那么创建一个显式返回数组会更快一些:收集/获取大约为 15k,而 Array/push 大约为 19k,但这是假设每个item 是最终需要的。
  • 哦,哇,我没想到。我测量了我的初始代码和 eqiv 之间接近 100% 的速度差异。带有显式数组和推送的代码。知道为什么聚集这么慢吗?
  • 鉴于这个问题,我添加了一个模块:String::Fields。它的界面略有不同,但我认为它在其他情况下也更灵活,更有用。

标签: raku


【解决方案1】:

我个人会将其拆分为一个列表,使用rotor 将列表拆分并加入结果:

"0019ABX26002".comb().rotor(3,1,4,*).map(*.join)

如果你想在函数处拆分(使用给定的索引):

sub split-at( $str, *@idxs ) { 
    my @rotors = @idxs.map( { state $l = 0; my $o = $_ - $l; $l = $_; $o } );
    $str.comb("").rotor( |@rotors,* ).map(*.join("")); 
}

基本上,如果我想做列表类型的东西,我会使用列表。

我从函数式编程的角度想出了另一个我非常喜欢的版本:

sub split-at( $str, *@idxs ) {
    (|@idxs, $str.codes)
    ==> map( { state $s = 0;my $e = $_ - $s;my $o = [$s,$e]; $s = $_; $o } )
    ==> map( { $str.substr(|$_) } );
}

它的运行速度比另一个慢一些。

【讨论】:

  • 嗯嗯。我们的想法相似,而且几乎同时 :-)
  • 谢谢你提醒我rotor的存在。不过在这种情况下。您正在为应该简单的操作做很多工作。
【解决方案2】:

一种方式:

.say for "0019ABX26002" .split: / <?{ $/.pos ∈ (3,4,8) }> /

显示:

001
9
ABX2
6002

【讨论】:

  • 整洁。但相当复杂。
  • 嗨,霍利。我赞成你们的 cmets 表示同意他们所有人,包括它很慢。 /// 作为我的正则表达式方法的安慰奖,可以将原来的== 3|4|8 替换为∈ @pos 以提高速度。 (有些人可能更喜欢它的阅读方式。)
【解决方案3】:

因为每个子字符串不依赖另一个,hyper 成为一个选项。

method split-at(\p) {
  do hyper for (0,|p) Z (|p,self.chars) {
    self.substr: .head, .tail - .head
  }
}

或以子形式:

sub split-at(\s, \p) {
  do hyper for (0,|p) Z (|p,s.chars) {
    s.substr: .head, .tail - .head
  }
}

但是除非请求的元素数量非常多,否则所涉及的开销是不值得的——在我的测试中,它比原始形式慢了大约十倍。

【讨论】:

    【解决方案4】:

    这是我将使用的解决方案:

    my method break (Str \s: *@i where .all ~~ Int) {
      gather for @i Z [\+] 0,|@i -> ($length, $start) {
        take s.substr: $start, $length
      }
    }
    
    say "abcdefghi".&break(2,3,4)   # "ab","cde","fghi"
    

    gather/take 如果您最终不需要全部使用它们,则可以让它变得懒惰。循环采用@i(示例中为2,3,4)并使用级联加法reducer [\+] 压缩它,这通常会产生2,5,9,但我们插入一个0 使其成为0,2,5,9 以标记开始每一项的索引。这让实际的操作变成了一个简单的substr 操作。

    通过将其设置为 method 而不是 sub,您可以像使用它一样使用它(如果需要,您甚至可以将其命名为 split,添加 &amp; sigil 意味着 Raku 不会搞不清楚你是想要内置的还是定制的。

    你甚至可以直接将它添加到 Str:

    use MONKEY-TYPING;   # enable augment
    augment class Str {
      multi method split (Str \s: *@i where .all ~~ Int) {
        gather for @i Z [\+] 0,|@i -> ($length, $start) {
          take s.substr: $start, $length
        }
      }
    }
    
    say "abcdefghi".split(2,3,4)
    

    在这种情况下,它需要定义为multi method,因为已经有各种split 方法。好消息是,由于这些都不是仅由 Int 参数定义的,因此很容易确保使用我们的增强型。

    也就是说,在词法 method 中使用 sigiled 版本调用它绝对是更好的选择。

    【讨论】:

    • ACk,我刚刚意识到你更喜欢 :at 命名参数,我会更新来做到这一点。
    • 我不喜欢它本身。我希望它是语言。这很常见。我们已经有六种split 的变体。这样的一个将是一个合理的补充,恕我直言。
    • Holli:实际上,我认为combsplit 更有意义,因为comb 已经设计用于处理整数。这个怎么样? tio.run/##VVLJasMwFLz7KwYTgk1dZyn0kODQaw@FQo4lLbIs26LygiTThCy/…
    • 另外,还可以添加核心,可以在github.com/Raku/problem-solving 中提出。在这种情况下,我认为 comb() 的提议可能很容易获得批准,尽管它可能要到 6.f 才能成为核心(不确定 6.e 是否仍然开放)
    • 您的解决方案将长度作为输入,而不是位置。
    最近更新 更多