This is my response to The Weekly Challenge #280.
$str
, containing lowercase English letters only.
Input: $str = "acbddbca"
Output: "d"
Example 2:
Input: $str = "abccd"
Output: "c"
Example 3:
Input: $str = "abcdabbb"
Output: "a"
Brute force, i.e. a loop, is probably the best approach:
File: twice-appearance
#! /usr/bin/env raku
unit sub MAIN ($str where $str ~~ /^<[a..z]>+$/); # [1]
my %seen; # [2]
for $str.comb -> $letter # [3]
{
if %seen{$letter} # [4]
{
say $letter; # [4a]
last; # [4b]
}
%seen{$letter} = True; # [5]
}
[1] Ensure that the string contains lowercase English letters only, one or more.
[2] Already encountered letters will be stored here.
[3] Iterate over each letter in the word.
[4] Have we seen the current letter before? If so, print it [4a] and exit [4b].
[5] Mark the current letter as seen.
Running it:
$ ./twice-appearance acbddbca
d
$ ./twice-appearance abccd
c
$ ./twice-appearance abcdabbb
a
Looking good.
No verbose mode, but here are some additional examples:
$ ./twice-appearance 12
Usage:
./twice-appearance [-v|--verbose[=Any]] <str>
$ ./twice-appearance abcdefg
We can make it shorter, almost a one liner, with map
instead of the explicit
loop:
#! /usr/bin/env raku
unit sub MAIN ($str where $str ~~ /^<[a..z]>+$/);
$str.comb.map({ state %seen; (say $_; last) if %seen{$_}++ });
Note the state
variable declared inside the map
.
See
docs.raku.org/syntax/state
for more information about the variable declarator state
.
See docs.raku.org/routine/map for more information about map
.
Running this program gives the expected result:
$ ./twice-appearance-map acbddbca
d
$ ./twice-appearance-map abccd
c
$ ./twice-appearance-map abcdabbb
a
$str
, where every two consecutive vertical bars are
grouped into a pair
.
*
, excluding any between
each pair of vertical bars.
Input: $str = "p|*e*rl|w**e|*ekly|"
Ouput: 2
The characters we are looking here are "p" and "w**e".
Example 2:
Input: $str = "perl"
Ouput: 0
Example 3:
Input: $str = "th|ewe|e**|k|l***ych|alleng|e"
Ouput: 5
The characters we are looking here are "th", "e**", "l***ych" and "e".
If we split a string on, let us say a comma, we get the parts without the commas. This is indeed what happens on parsing a CSV (comma separated values) file.
An example:
> "This,does,not,make,sense".split(",").raku
("This", "does", "not", "make", "sense").Seq
We can split on regular expressions instead of single characters (or strings).
> "This,does not;make,any sense".split(/<[\ ,;.]>+/).raku
("This", "does", "not", "make", "any", "sense").Seq
So it should not come as a big surprise that we can use a pattern matching the pairs of vertical bars, and split on that.
File: count-asterisks
#! /usr/bin/env raku
unit sub MAIN ($str, :v(:$verbose)); # [1]
my @parts = $str.split(/\|.*?\|/); # [2]
say ": Parts: { @parts.raku }" if $verbose;
@parts.join.comb.grep( * eq '*').elems.say; # [3]
[1] No restrictions on the string this time.
[2] Split on pairs of vertical bars (with some non-«vertical bars» between them).
[3] Join the resulting array, split it into individual characters, get rid of non-asterisks, and count the result (i.e. the asterisks).
Running it:
$ ./count-asterisks "p|*e*rl|w**e|*ekly|"
2
$ ./count-asterisks "perl"
0
$ ./count-asterisks "th|ewe|e**|k|l***ych|alleng|e"
5
Looking good.
With verbose mode:
$ ./count-asterisks -v "p|*e*rl|w**e|*ekly|"
: Parts: ["p", "w**e", ""]
2
$ ./count-asterisks -v "perl"
: Parts: ["perl"]
0
$ ./count-asterisks -v "th|ewe|e**|k|l***ych|alleng|e"
: Parts: ["th", "e**", "l***ych", "e"]
5
And that's it.