Разделить строку в заданных позициях - PullRequest
8 голосов
/ 22 января 2020

Как мне красиво / идиоматически разделить строку в списке позиций?

Что у меня есть:

.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 ]

9 голосов
/ 22 января 2020

Лично я бы разделил его на список, используйте 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(|$_) } );
}

Она работает немного медленнее, чем другая.

4 голосов
/ 22 января 2020

В одну сторону:

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

Отображение:

001
9
ABX2
6002
3 голосов
/ 23 января 2020

Поскольку каждая подстрока не зависит от другой, гипер становится опцией.

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

Но связанные с этим издержки не стоят того, если количество запрошенных элементов чрезвычайно велико - в моих тестах оно примерно в десять раз медленнее, чем наивная форма.

2 голосов
/ 22 января 2020

Вот решение, которое я бы использовал:

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 позволяет быть ленивым, если в конечном итоге вам не нужно использовать их все. L oop берет @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, определенно лучше.

...