Saite an bestimmten Positionen teilen

8

Wie teile ich eine Zeichenfolge an einer Liste von Positionen schön / idiomatisch auf?

Was ich habe:

.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, * );
  }
}

das ist vernünftig. Ich bin jedoch verwirrt über den Mangel an Sprachunterstützung dafür. Wenn "Teilen auf" eine Sache ist, warum wird "Teilen auf" nicht auch? Ich denke, dies sollte eine Kernoperation sein. Ich sollte schreiben können

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

Oder übersehe ich vielleicht etwas?

Edit: Ein kleiner Benchmark von dem, was wir bisher haben

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

Code:

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;
}
Holli
quelle
Dafür erwarten Sie also : ("001", "9", "ABX2", "6002")?
Scimon Proctor
In diesem Fall wäre das die Ausgabe ja.
Holli
Wenn Sie auf der Suche nach roher Geschwindigkeit sind, ist das Erstellen eines expliziten Rückgabearrays ein gutes Stück schneller: Ich erhalte ~ 15.000 mit Gather / Take gegenüber fast 19.000 mit Array / Push, aber dies setzt voraus, dass jedes Element letztendlich benötigt wird.
user0721090601
Oh wow, das habe ich nicht erwartet. Ich messe einen Geschwindigkeitsunterschied von nahezu 100% zwischen meinem ursprünglichen Code und dem EQIV. Code mit einem expliziten Array und Pushs. Irgendeine Idee, warum das Sammeln so viel langsamer ist?
Holli
2
In Anbetracht dieser Frage habe ich ein Modul hinzugefügt: String :: Fields . Die Benutzeroberfläche ist etwas anders, aber ich denke, sie ist auch in anderen Situationen flexibler und nützlicher.
Elizabeth Mattijsen

Antworten:

9

Persönlich würde ich es in eine Liste rotoraufteilen , verwenden , um die Liste aufzuteilen und das Ergebnis zu verbinden:

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

Wenn Sie eine Aufteilung bei Funktion wünschen (unter Verwendung der angegebenen Indizes):

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

Grundsätzlich verwende ich eine Liste, wenn ich Dinge vom Listentyp machen möchte.

Ich habe mir eine andere Version ausgedacht, die ich aus funktionaler Programmierrichtung wirklich mag:

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

Es ist etwas langsamer als das andere.

Scimon Proctor
quelle
2
Na gut. Wir denken gleich und fast gleichzeitig :-)
jjmerelo
Vielen Dank, dass Sie mich an die Existenz von erinnert haben rotor. In diesem Fall allerdings. Sie erledigen eine Menge Arbeit für eine einfache Bedienung.
Holli
4

Einweg:

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

zeigt an:

001
9
ABX2
6002
Raiph
quelle
1
Ordentlich. Aber ziemlich kompliziert.
Holli
1
Auch sehr langsam. Schauen Sie sich den Benchmark an
Holli
1
Hallo Holli. Ich habe Ihre Kommentare positiv bewertet, um die Zustimmung zu allen zu signalisieren, einschließlich der Tatsache, dass es langsam ist. /// Als Trostpreis für meinen Regex-Ansatz kann man das Original == 3|4|8durch ersetzen ∈ @pos, um die Geschwindigkeit zu verbessern. (Und einige mögen es vielleicht auch vorziehen, wie es liest.)
Raiph
3

Da jeder Teilstring nicht vom anderen abhängt, wird Hyper zu einer Option.

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

Oder in Unterform:

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

Der Aufwand lohnt sich jedoch nur, wenn die Anzahl der angeforderten Elemente extrem ist - in meinen Tests ist er etwa zehnmal langsamer als die naive Form.

user0721090601
quelle
2

Hier ist die Lösung, die ich verwenden würde:

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"

Das gather/ takelässt es faul sein, wenn Sie letztendlich nicht alle verwenden müssen. Die Schleife nimmt @i( 2,3,4im Beispiel) und komprimiert sie mit dem kaskadierenden Additionsreduzierer [\+], der normalerweise erzeugt 2,5,9wird. Wir fügen jedoch eine 0 ein, um 0,2,5,9die Startindizes jedes einzelnen zu markieren. Dies ermöglicht eine einfache substrOperation.

Wenn Sie es methodzu einem Sub machen, können Sie es so verwenden, wie Sie es möchten (Sie können es sogar benennen, splitwenn Sie möchten. Das Hinzufügen des &Siegels bedeutet, dass Raku nicht verwirrt ist, ob Sie das eingebaute oder das speziell angefertigte möchten.

Sie können es sogar direkt zu Str hinzufügen:

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)

In diesem Fall muss definiert werden, multi methoddass es bereits verschiedene splitMethoden gibt. Das Schöne ist, da keines davon nur durch IntArgumente definiert ist , ist es einfach sicherzustellen, dass unser erweitertes verwendet wird.

Das heißt, es methodist definitiv besser , es mit der Sigiled-Version in einem Lexikon zu bezeichnen.

user0721090601
quelle
ACk, ich habe gerade festgestellt, dass Sie einen :atbenannten Parameter bevorzugen . Ich werde dies aktualisieren.
user0721090601
Ich bevorzuge es nicht per se. Ich möchte, dass es in der Sprache ist. Es ist häufig genug. Wir haben bereits ein halbes Dutzend Varianten von split. Ein solcher wäre eine vernünftige Ergänzung, imho.
Holli
Holli: Eigentlich denke ich, dass es mehr Sinn macht combals in split, da comb bereits für die Arbeit mit ganzen Zahlen ausgelegt ist. Wie wäre es damit? tio.run/##VVLJasMwFLz7KwYTgk1dZyn0kODQaw@FQo4lLbIs26LygiTThCy/…
user0721090601
Außerdem können Kernerweiterungen vorgenommen und unter github.com/Raku/problem-solving vorgeschlagen werden . In diesem Fall würde ich denken, dass ein Vorschlag für comb () ziemlich einfach genehmigt werden kann, obwohl er möglicherweise erst in 6.f in den Kern gelangt (nicht sicher, ob 6.e noch offen ist)
user0721090601
Ihre Lösung erwartet Längen als Eingabe, keine Positionen.
Holli