在给定位置分割字符串

时间:2020-01-22 17:06:46

标签: raku

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

我所拥有的:

.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;
}

4 个答案:

答案 0 :(得分:9)

我个人将其拆分为一个列表,使用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(|$_) } );
}

事实证明它比另一个慢一些。

答案 1 :(得分:4)

一种方法:

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

显示:

001
9
ABX2
6002

答案 2 :(得分: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
  }
}

但是,除非请求的元素数量过多,否则所涉及的开销是不值得的-在我的测试中,它比纯朴的表单慢大约十倍。

答案 3 :(得分:2)

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

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),并使用级联加减径器[\+]进行压缩,通常会产生2,5,9,但是我们插入一个0以使0,2,5,9标记每个索引的起始索引。这样一来,实际操作就变成了简单的substr操作。

通过将其设置为method而不是子名称,您可以像使用它一样使用它(如果需要,甚至可以命名为split,还可以添加&意味着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中的标记版本来称呼它肯定是更好的选择。