指定された位置で文字列を分割する


8

位置のリストで文字列をうまく/慣用的に分割するにはどうすればよいですか?

私が持っているもの:

.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")
Scimon Proctor

この場合、それは出力yesになります。
Holli

生の速度を求めている場合は、明示的な戻り配列を作成する方がかなり高速です:収集/テイクでは約15,000、配列/プッシュでは約19kですが、各アイテムが最終的に必要であると想定しています。
user0721090601

ああ、それは予想していませんでした。最初のコードとeqivの間のほぼ100%の速度差を測定します。明示的な配列とプッシュを使用したコード。なぜ収集が非常に遅いのか考えてみませんか?
Holli

2
この質問に照らして、次のモジュールを追加しました:String :: Fields。インターフェースは少し異なりますが、他の状況でもより柔軟で便利だと思います。
Elizabeth Mattijsen、

回答:


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(|$_) } );
}

それは他のものよりわずかに遅いことがわかった。


2
ええと。私たちは同じように考えると同時に、ほぼ同時に:-)
jjmerelo

の存在を思い出させてくれてありがとうrotor。この場合でも。あなたは、単純な操作であるべきもののために多くの仕事をしています。
Holli

4

一方通行:

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

表示:

001
9
ABX2
6002

1
きちんと。しかし、かなり複雑です。
Holli

1
また、非常に遅いです。ベンチマークをチェック
Holli

1
こんにちは、ホーリー。私はあなたのコメントを賛成して、遅いことを含め、彼ら全員との合意を示しました。///私の正規表現アプローチの慰めの賞として、速度を改善するためにオリジナル== 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
  }
}

ただし、要求される要素の数が極端でない限り、含まれるオーバーヘッドは価値がありません。私のテストでは、単純なフォームよりも約10倍遅くなっています。


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あなたが最終的にそれらのすべてを使用する必要がない場合、それは怠惰なことができます。ループがかかる@i2,3,4例では)とカスケード加算減速でそれをzip圧縮[\+]通常生じるであろう、2,5,9しかし、我々はそれを作るために0を挿入し0,2,5,9、各1の開始インデックスをマークします。これにより、実際のテイクを簡単な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方が間違いなく優れています。


ACk、私はあなたが:at名前付きパラメーターを好むことに気付いたので、それを行うために更新します。
user0721090601

それ自体は好きではありません。私はそれがその言語であることを望みます。それは十分に一般的です。すでに6種類の亜種がありsplitます。そのようなものは合理的な追加です、imho。
Holli

Holli:実際、combはすでに整数で動作するように設計されているので、combではなくでより意味があると思いsplitます。これはどう?tio.run/##VVLJasMwFLz7KwYTgk1dZyn0kODQaw@FQo4lLbIs26LygiTThCy/...
user0721090601

また、コアの追加を行うことができ、github.com/Raku/problem-solvingで提案できます。この場合、comb()へのプロポーザルは、6.fまでコアになることはないかもしれませんが(6.eがまだオープンかどうかわからない)、承認を受けるのはかなり簡単かもしれないと思います
user0721090601

あなたのソリューションは、ポジションではなく入力として長さを期待しています。
Holli
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.