FAQ: In Raku, how do you remove some characters from a string, based on their index?
Say I want to remove indices 1 to 3 and 8
xxx("0123456789", (1..3, 8).flat); # 045679
Variant of Shnipersons answer:
my $a='0123456789';
with $a {$_=.comb[(^* ∖ (1..3, 8).flat).keys.sort].join};
say $a;
In one line:
say '0123456789'.comb[(^* ∖ (1..3, 8).flat).keys.sort].join;
or called by a function:
sub remove($str, $a) {
$str.comb[(^* ∖ $a.flat).keys.sort].join;
}
say '0123456789'.&remove: (1..3, 8);
or with augmentation of Str:
use MONKEY-TYPING;
augment class Str {
method remove($a) {
$.comb[(^* ∖ $a.flat).keys.sort].join;
}
};
say '0123456789'.remove: (1..3, 8);
My latest idea for a not-at operation (I'll cover the implementation below):
Usage:
say '0123456789'[- 1..3, 8 ]; # 045679
Implementation, wrapping (a variant of) Brad's solution:
multi postcircumfix:<[- ]> (|args) { remove |args }
sub remove( Str:D $str is copy, +@exdices){
for @exdices.reverse {
when Int { $str.substr-rw($_,1) = '' }
when Range { $str.substr-rw($_ ) = '' }
}
$str
}
say '0123456789'[- 1..3, 8 ]; # 045679
The syntax to use the operator I've declared is string[- list-of-indices-to-be-subtracted ]
, i.e. using familiar [...]
notation, but with a string on the left and an additional minus after the opening [
to indicate that the subscript contents are a list of exdices rather than indices.
[Edit: I've replaced my original implementation with Brad's. That's probably wrong-headed because, as Brad notes, his solution "assumes that the [exdices] are in order from lowest to highest, and there is no overlap.", and while his doesn't promise otherwise, using [- ... ]
is awfully close to doing so. So if this syntax sugar were to be used by someone, they should probably not use Brad's solution. Perhaps there is a way to eliminate the assumption Brad's makes.]
I like this syntax but am aware that Larry deliberately did not build in use of [...]
to index strings so perhaps my syntax here is inappropriate for widespread adoption. Perhaps it would be better if some different bracketing characters were used. But I think use of a simple postcircumfix syntax is nice.
(I've also tried to implement a straight [ ... ]
variant for indexing strings in exactly the same way as for Positional
s but have failed to get it to work for reasons beyond me tonight. Weirdly [+ ... ]
will work to do exdices but not to do indices; that makes no sense to me at all! Anyhow, I'll post what I have and consider this answer complete.)
[Edit: The above solution has two aspects that should be seen as distinct. First, a user-defined operator, the syntactic sugar provided by the postcircumfix:<[- ]> (Str ...
, declaration. Second, the body of that declaration. In the above I've used (a variant of) Brad's solution. My original answer is below.]
Because your question boils down to removing some indices of a [Edit: Wrong, per Brad's answer.].comb
, and rejoin
ing the result, your question is essentially a duplicate of ...
What is a quick way to de-select array or list elements? adds yet more solutions for the [.comb ... .join
] answers here.
Implemented as two multis so the same syntax can be used with Positional
s:
multi postcircumfix:<[- ]> (Str $_, *@exdex) { .comb[- @exdex ].join }
multi postcircumfix:<[- ]> (@pos, *@exdex) { sort keys ^@pos (-) @exdex }
say '0123456789'[- 1..3, 8 ]; # 045679
say (0..9)[- 1..3, 8 ]; # (0 4 5 6 7 9)
The sort keys ^@pos (-) @exdices
implementation is just a slightly simplified version of @Sebastian's answer. I haven't benchmarked it against jnthn's solution from the earlier answer I linked above but if that's faster then it can be swapped in instead. *[Edit: Obviously it should instead be Brad's solution for the string variant.]*
Everyone is either turning the string into a list using comb
or using a flat list of indices.
There is no reason to do either of those things
sub remove( Str:D $str is copy, +@indices ){
for @indices.reverse {
when Int { $str.substr-rw($_,1) = '' }
when Range { $str.substr-rw($_ ) = '' }
}
$str
}
remove("0123456789", 1..3, 8 ); # 045679
remove("0123456789", [1..3, 8]); # 045679
The above assumes that the indices are in order from lowest to highest, and there is no overlap.
yet another variants:
print $_[1] if $_[0] !(elem) (1,2,3,8) for ^Inf Z 0..9;
.print for ((0..9) (-) (1,2,3,8)).keys;
This is the closest I got in terms of simplicity and shortness.
say '0123456789'.comb[ |(3..6), |(8..*) ].join
my $string='0123456789';
for (1..3, 8).flat.reverse { $string.substr-rw($_, 1) = '' }
I know this is an old-ish question, but here's another version that takes a similar approach to Brad Gilbert's substr-rw
solution but avoids needing to edit the string in-place (and thus fits better with a functional-programming mindset):
sub remove(Str:D $str, +@indices) {
($str, |@indices.reverse).reduce: -> $_, $idx {
{ S/.**{$idx.head} <(.**{$idx.elems})> .*// }}
}
say remove "0123456789", 1..3, 8; # OUTPUT: «045679»
say remove "0123456789", [1..3, 8]; # OUTPUT: «045679»
This is not that different from the substr-rw
solution when both are wrapped up in a function. But the version above lends itself to better inline use, at least imo:
say S/.**5 <(.**3)> .*// with "0123456789"; # OUTPUT: «0123489»
instead of
say do with "0123456789" -> $_ is copy { .substr-rw(5..7) = ''; $_ } # OUTPUT: «0123489»
Note, however, that the S///
-based approach isn't any faster than the .comb
approaches so using .substr-rw
could be useful in performance-critical areas.