Unique Trim
with Raku and Perl

by Arne Sommer

Unique Trim with Raku and Perl

[199] Published 3. September 2022.

This is my response to The Weekly Challenge #180.

Challenge #180.1: First Unique Character

You are given a string, $s.

Write a script to find out the first unique character in the given string and print its index (0-based).

Example 1:
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

To Bag or not to Bag?

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.

A Perl Version

This is straight forward translation of the Raku version.

File: first-unique-character-perl
#! /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"
    

Challenge #180.2: Trim List

You are given list of numbers, @n and an integer $i.

Write a script to trim the given list where element is less than or equal to the given integer.

Example 1:
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)
File: trim-list
#! /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.

Perl

This is a straight forward translation of the Raku version.

File: trim-list-perl
#! /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.