This is my response to The Weekly Challenge #180.
$s
.
Input: $s = "Perl Weekly Challenge"
Output: 0 as 'P' is the first unique character
Example 2:
Input: $s = "Long Live Perl"
Output: 1 as 'o' is the first unique character
We need a way of counting the frequency of the characters. We could
do this with a loop and a traditional hash (and will do so in the Perl version, later
on), but using the Bag
type is easier - as in gives shorter code:
> my $b = "Perl Weekly Challenge".comb.Bag; # [1]
> say $b; # [2]
Bag( (2) C P W a e(5) g h k l(4) n r y)
> say $b.raku; # [3]
("k"=>1,"y"=>1,"W"=>1,"P"=>1,"C"=>1,"a"=>1,"g"=>1,"h"=>1,"r"=>1, \
"e"=>5,"l"=>4," "=>2,"n"=>1).Bag
> say $b{'e'}; # [4]
5
> say $b<C>; # [5]
1
> say $b<q>; # [6]
0
[1] Set up a Bag
with the individual characters,
courtesy of the comb
.
See
docs.raku.org/type/Bag
for more information about the Bag
type.
See
docs.raku.org/routine/comb
for more information about comb
.
[2] We can print it, and it will sort of be readable.
[3] Using the raku
method gives nicer output.
Note the space character (the last but one), compared with how it was shown in [2];
the first entry.
See
docs.raku.org/routine/raku
for more information about raku
.
[4] Looking up a character gives the frequency.
[5] We can use this shortcut with hard coded keys, skipping the quotes..
[6] A non-existent character (key) gives 0.
Then the program:
File: first-unique-character
#! /usr/bin/env raku
unit sub MAIN (Str $s, :v(:$verbose)); # [1]
my @s = $s.comb; # [2]
my $freq = @s.Bag; # [3]
for ^@s.elems -> $index # [4]
{
my $char = @s[$index]; # [5]
my $count = $freq{$char}; # [6]
say ": Char $char with frequency $count" if $verbose;
if $count == 1 # [7]
{
say $index; # [7a]
last; # [7b]
}
}
[1] The string (as a string).
[2] Get a list of individual characters.
[3] Get the Bag
.
[4] Iterate over the indices in the array (and string).
[5] The current character.
[6] Get the count for the current character.
[7] Only one istance? If so, print the index and we are done.
Running it:
$ ./first-unique-character "Perl Weekly Challenge"
0
$ ./first-unique-character "Long Live Perl"
1
With verbose mode:
$ ./first-unique-character -v "Perl Weekly Challenge"
: Char P with frequency 1
0
$ ./first-unique-character -v "Long Live Perl"
: Char L with frequency 2
: Char o with frequency 1
1
Looking good.
Note that the challenge does not say what to do if there are no unique characters in the string. Printing «-1» is a possibility, as it printing nothing. The program actually does the latter:
$ ./first-unique-character "this is a hoot and not a toad"
$ ./first-unique-character -v "this is a hoot and not a toad"
: Char t with frequency 4
: Char h with frequency 2
: Char i with frequency 2
: Char s with frequency 2
: Char with frequency 7
: Char i with frequency 2
: Char s with frequency 2
: Char with frequency 7
: Char a with frequency 4
: Char with frequency 7
: Char h with frequency 2
: Char o with frequency 4
: Char o with frequency 4
: Char t with frequency 4
: Char with frequency 7
: Char a with frequency 4
: Char n with frequency 2
: Char d with frequency 2
: Char with frequency 7
: Char n with frequency 2
: Char o with frequency 4
: Char t with frequency 4
: Char with frequency 7
: Char a with frequency 4
: Char with frequency 7
: Char t with frequency 4
: Char o with frequency 4
: Char a with frequency 4
: Char d with frequency 2
If you want «-1», replace the last
in [7b] with exit
, and add a new line
say -1
after the last line in the program.
#! /usr/bin/env perl
use strict;
use warnings;
use feature 'say';
use Getopt::Long;
my $verbose = 0;
GetOptions("verbose" => \$verbose);
my $s = $ARGV[0];
my @s = split(//, $s);
my %freq;
for my $char (@s) # [1]
{
$freq{$char}++;
}
for my $index (0 .. @s -1)
{
my $char = $s[$index];
my $count = $freq{$char};
say ": Char $char with frequency $count" if $verbose;
if ($count == 1)
{
say $index;
last;
}
}
[1] This loop can be (re)written more compact using map
, like
this: map { $freq{$_}++ } @s
.
Running it gives the same result as the Raku version, even with verbose mode:
$ ./first-unique-character-perl -v "Perl Weekly Challenge"
: Char P with frequency 1
0
$ ./first-unique-character-perl -v "Long Live Perl"
: Char L with frequency 2
: Char o with frequency 1
1
$ ./first-unique-character-perl "this is a hoot and not a toad"
@n
and an integer $i
.
less than or equal to
the given integer.
Input: @n = (1,4,2,3,5) and $i = 3
Output: (4,5)
Example 2:
Input: @n = (9,0,6,2,3,8,5) and $i = 4
Output: (9,6,8,5)
#! /usr/bin/env raku
unit sub MAIN (Int $i, *@n where @n.elems > 0 && all(@n) ~~ Numeric); # [1]
say @n.grep: * > $i; # [2]
[1] The first value goes to $i
, and the rest to the array @n
with a slurpy *
prefix. Then we ensure that all the values (of which there
are at least 1) are numeric.
[2] Use grep
to only let values larger than $i
through.
Running it gives the expected result, except the commas:
$ ./trim-list 3 1 4 2 3 5
(4 5)
$ ./trim-list 4 9 0 6 2 3 8 5
(9 6 8 5)
Adding commas is easyish:
File: trim-list-comma
#! /usr/bin/env raku
unit sub MAIN (Int $i, *@n where @n.elems > 0 && all(@n) ~~ Numeric);
say "(", @n.grep( * > $i ).join(","), ")"; # [1]
[1] Not as compact as in the first version, though...
Running it:
$ ./trim-list-comma 3 1 4 2 3 5
(4,5)
$ ./trim-list-comma 4 9 0 6 2 3 8 5
(9,6,8,5)
What about an empty list?
$ ./trim-list 9 1 4 2 3 5
()
$ ./trim-list-comma 9 1 4 2 3 5
()
That is ok.
#! /usr/bin/env perl
use strict;
use warnings;
use feature 'say';
my $i = shift(@ARGV); # [1]
my @n = @ARGV; # [2]
say "(", join(",", grep { $_ > $i } @n), ")"; # [3]
[1] The first value goes into $i
.
[2] The rest of them go into @n
.
[3] Notice how much easier the Raku version is to read.
Running it gives the same result as the Raku version:
$ ./trim-list-perl 3 1 4 2 3 5
(4,5)
$ ./trim-list-perl 4 9 0 6 2 3 8 5
(9,6,8,5)
$ ./trim-list-perl 9 1 4 2 3 5
()
And that's it.