An Abundance of Strings
with Raku

by Arne Sommer

An Abundance of Strings with Raku

[213] Published 4. December 2022.

This is my response to The Weekly Challenge #193.

Challenge #193.1: Binary String

You are given an integer, $n > 0.

Write a script to find all possible binary numbers of size $n.

Example 1:
Input: $n = 2
Output: 00, 11, 01, 10
Example 2:
Input: $n = 3
Output: 000, 001, 010, 100, 111, 110, 101, 011

File: binary-string-loop
#! /usr/bin/env raku

unit sub MAIN (Int $n where $n > 0);

my $decimal = 0;                               # [1]

my @binary;                                    # [2]

loop                                           # [3]
{
  my $binary = $decimal.fmt('%0' ~ $n ~ 'b');  # [4]
  last if $binary.chars > $n;                  # [5]

  @binary.push: $binary;                       # [6]
  $decimal++;                                  # [7]
}

say @binary.join(", ");                        # [8]

[1] The loop variable (in decimal), from zero up to the limit.

[2] The binary numbers will end up here, as strings.

[3] An eternal loop. Note the exit clause in [5].

[4] Convert the loop value to binary (with fmt), as a zero padded string giving the total length as $n).

See docs.raku.org/routine/fmt for more information about fmt.

[5] exit the loop if we aquired too many digits in the binary representation.

[6] Add the (binary, zero padded) value to the list.

[7] The next decimal value.

[8] Print the list nicely.

Running it:

$ ./binary-string-loop 2
00, 01, 10, 11

$ ./binary-string-loop 3
000, 001, 010, 011, 100, 101, 110, 111

I have chosen to print the values sorted by value (increasing). The challenge printed them in a rather random order (e.g. the highest value).

It is certainly possible to make the program shorter. Here it is as a one liner, if you ignore the crash-bang and unit lines:

File: binary-string-map
#! /usr/bin/env raku

unit sub MAIN (Int $n where $n > 0);

say (0 .. (1 x $n).parse-base(2)).map( *.fmt('%0' ~ $n ~ 'b') ).join(", ");
### # 1 ########################## # 2 ######################### # 3 #######

[1] Start with all the decimal values (from "0" to e.g. "8" (binary "1111") if $n == 4, using the string repetition operator x and parse-base).

See docs.raku.org/routine/x for more information about the string repetition operator x.

See docs.raku.org/routine/parse-base for more information about parse-base.

[2] Convert to binary, zero padded.

[3] Join the values, and let the say up front print the lot.

Running it gives the same result as first version:

$ ./binary-string-map 2
00, 01, 10, 11

$ ./binary-string-map 3
000, 001, 010, 011, 100, 101, 110, 111

Challenge #193.2: Odd String

You are given a list of strings of same length, @s.

Write a script to find the odd string in the given list. Use positional value of alphabet starting with 0, i.e. a = 0, b = 1, ... z = 25.

Find the difference array for each string as shown in the example. Then pick the odd one out.

Example 1:
Input: @s = ("adc", "wzy", "abc")
Output: "abc"

Difference array for "adc" => [ d - a, c - d ]
                           => [ 3 - 0, 2 - 3 ]
                           => [ 3, -1 ]

Difference array for "wzy" => [ z - w, y - z ]
                           => [ 25 - 22, 24 - 25 ]
                           => [ 3, -1 ]

Difference array for "abc" => [ b - a, c - b ]
                           => [ 1 - 0, 2 - 1 ]
                           => [ 1, 1 ]

The difference array for "abc" is the odd one.
Example 2:
Input: @s = ("aaa", "bob", "ccc", "ddd")
Output: "bob"

Difference array for "aaa" => [ a - a, a - a ]
                           => [ 0 - 0, 0 - 0 ]
                           => [ 0, 0 ]

Difference array for "bob" => [ o - b, b - o ]
                           => [ 14 - 1, 1 - 14 ]
                           => [ 13, -13 ]

Difference array for "ccc" => [ c - c, c - c ]
                           => [ 2 - 2, 2 - 2 ]
                           => [ 0, 0 ]

Difference array for "ddd" => [ d - d, d - d ]
                           => [ 3 - 3, 3 - 3 ]
                           => [ 0, 0 ]

The difference array for "bob" is the odd one.

File: odd-string
#! /usr/bin/env raku

unit sub MAIN (*@s where ( [==] @s>>.chars), :v(:$verbose));  # [1]

my %diff;                                                     # [2]

for @s -> $string                                             # [3]
{
  my @letters = $string.comb;                                 # [4]
  my @diff;                                                   # [5]
  my $first;                                                  # [6]
  my $second = @letters.shift;                                # [7]
  while (@letters.elems) {                                    # [8]
    my $first = $second;                                      # [9]
    $second = @letters.shift;                                 # [10]
    my $diff = $second.ord - $first.ord;                      # [11]
    @diff.push: $diff;                                        # [12]
  }

  %diff{ @diff.join: " " }.push: $string;                     # [13]
  say ": $string -> @diff[]" if $verbose;
}

say ":" ~ %diff.raku if $verbose;

say %diff.grep({ $_.value.elems == 1 }).map( *.value ).Str;   # [14]

[1] Ensure that all the string lengths ( @s>>.chars) are identical ([==]). Note the missing check on legal letters only. (The challenge does not actually say that we are limited to lower case letters, though it implies it.)

[2] The difference array for each string will end up here.

[3] Iterate over each string.

[4] Get the individual letters.

[5] The difference values for the current string will end up here.

[6] The first value will be kept here,

[7] and the second one here. The initial state is messy, but will be corrected inside the loop.

[8] As long as we have more letters,

[9] Move the second one (from the previous iteration) up front, as the new first.

[10] Get a new second.

[11] Get the difference, using the unicode codepoints (from ord).

See docs.raku.org/routine/ord for more information about ord.

[12] Add that difference to the list.

[13] The difference string is the difference values added together (space separated). Note the use of push to add the value to a list - which we store in the hash.

[14] Print the string(s) that have a unique difference string (i.e. occur just once, courtesy of the grep). The map gives us the value, and not the hash pair (key, value)

Running it:

$ ./odd-string adc wzy abc
abc

$ ./odd-string aaa bob ccc ddd
bob

$ ./odd-string adc wzy abc 111
abc 111

With verbose mode:

$ ./odd-string -v aaa bob ccc ddd
: aaa -> 0 0
: bob -> 13 -13
: ccc -> 0 0
: ddd -> 0 0
:{"0 0" => $["aaa", "ccc", "ddd"], "13 -13" => $["bob"]}
bob

$ ./odd-string -v adc wzy abc 111
: adc -> 3 -1
: wzy -> 3 -1
: abc -> 1 1
: 111 -> 0 0
:{"0 0" => $[IntStr.new(111, "111")], "1 1" => $["abc"], "3 -1" => $["adc", "wzy"]}
abc 111

$ ./odd-string -v adc wzy abc 111 222
: adc -> 3 -1
: wzy -> 3 -1
: abc -> 1 1
: 111 -> 0 0
: 222 -> 0 0
:{"0 0" => $[IntStr.new(111, "111"), IntStr.new(222, "222")], "1 1" => $["abc"], "3 -1" => $["adc", "wzy"]}
abc

The odd one out...

The two examples leave no room for confusion, but what if we do not have one string with an unique difference string? As in my adc wzy abc 111?

Or even worse, if we have something like these:

$ ./odd-string -v adc wzy abc def
: adc -> 3 -1
: wzy -> 3 -1
: abc -> 1 1
: def -> 1 1
:{"1 1" => $["abc", "def"], "3 -1" => $["adc", "wzy"]}


$ ./odd-string -v adc wzy abc def ghi
: adc -> 3 -1
: wzy -> 3 -1
: abc -> 1 1
: def -> 1 1
: ghi -> 1 1
:{"1 1" => $["abc", "def", "ghi"], "3 -1" => $["adc", "wzy"]}

My program does not handle these examples, as it is hard to decide what to do in these cases.

And that's it.