Numbers and Values
with Raku

by Arne Sommer

Numbers and Values with Raku

[304] Published 20. August 2024.

This is my response to The Weekly Challenge #283.

Challenge #283.1: Unique Number

You are given an array of integers, @ints, where every elements appears more than once except one element.

Write a script to find the one element that appears exactly one time.

Example 1:
Input: @ints = (3, 3, 1)
Output: 1
Example 2:
Input: @ints = (3, 2, 4, 2, 4)
Output: 3
Example 3:
Input: @ints = (1)
Output: 1
Example 4:
Input: @ints = (4, 3, 1, 1, 1, 4)
Output: 3
File: unique-number
#! /usr/bin/env raku

unit sub MAIN (*@ints where all(@ints) ~~ Int && @ints.elems > 0,
               :v(:$verbose));                        # [1]

my $bag = @ints>>.Int.Bag;                            # [2]

say ": Bag { $bag.raku }" if $verbose;

say $bag.grep(*.value == 1)>>.key.sort.join(",");     # [3]

[1] Ensure that we get integers only, and at least one of them.

[2] Turn the input into a Bag, a hash like structure where the unique values are the keys and their frequency are the values.

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

[3] We use grep to get the entries with a frequency of 1, then we extract the keys from the entries, then we sort the result so that we get the same order each time we run this. And finally we pritty print the result.

Note that this program supports multiple unique entries, and not just one as prescribed.

Running it:

$ ./unique-number 3 3 1
1

$ ./unique-number 3 2 4 2 4
3

$ ./unique-number 1
1

$ ./unique-number 4 3 1 1 1 4
3

Looking good.

With verbose mode:


$ ./unique-number -v 3 3 1
: Bag (1=>1,3=>2).Bag
1

$ ./unique-number -v 3 2 4 2 4
: Bag (4=>2,2=>2,3=>1).Bag
3

$ ./unique-number -v 1
: Bag (1=>1).Bag
1

$ ./unique-number -v 4 3 1 1 1 4
: Bag (3=>1,1=>3,4=>2).Bag
3

Multiple unique entries do work out:

$ ./unique-number 1 2 3 4 5 5
1,2,3,4

$ ./unique-number -v 1 2 3 4 5 5
: Bag (4=>1,2=>1,5=>2,3=>1,1=>1).Bag
1,2,3,4

Challenge #283.2: Digit Count Value

You are given an array of positive integers, @ints.

Write a script to return true if for every index i in the range 0 <= i < size of array, the digit i occurs exactly the $ints[$i] times in the given array otherwise return false.

Example 1:
Input: @ints = (1, 2, 1, 0)
Ouput: true

$ints[0] = 1, the digit 0 occurs exactly 1 time.
$ints[1] = 2, the digit 1 occurs exactly 2 times.
$ints[2] = 1, the digit 2 occurs exactly 1 time.
$ints[3] = 0, the digit 3 occurs 0 time.
Example 2:
Input: @ints = (0, 3, 0)
Ouput: false

$ints[0] = 0, the digit 0 occurs 2 times rather than 0 time.
$ints[1] = 3, the digit 1 occurs 0 time rather than 3 times.
$ints[2] = 0, the digit 2 occurs exactly 0 time.

Note that 0 (zero) is not a positive integer. The challenge should have used «non-negative integers».

File: digit-count-value
#! /usr/bin/env raku

unit sub MAIN (*@ints where all(@ints) ~~ UInt && @ints.elems > 0, # [1]
	       :a(:$all),                                          # [1a]
               :v(:$verbose) = $all);                              # [1b]

my $bag    = @ints>>.Int.Bag;                                      # [2]
my $status = True;                                                 # [3]

say ": Bag: { $bag.raku }" if $verbose;

for ^@ints.elems -> $index                                         # [4]
{
  my $val   = @ints[$index];                                       # [5]
  my $count = $bag{$index};                                        # [6]

  my $is-ok = $val == $count;                                      # [7]

  say ": \@ints[{ $index }] = { $val }, the digit { $index } occurs \
    { $count } time(s) | { $is-ok ?? "OK" !! "Not OK" }" if $verbose;

  unless $is-ok                                                    # [8]
  {
    $status = False;                                               # [8a]
    last unless $all;                                              # [8b]
  }
}

say $status;                                                       # [9]

[1] Ensure that all the values are unsigned integers (the UInt type), and that we got at least one of them. Note the default value for the «verbose» argument, enabling it automatically if we use «all».

[2] Another Bag.

[3] Success so far.

[4] Iterate over the indices.

[5] Get the value,

[6] and the count (from the Bag).

[7] Do the value and count match up?.

[8] If not, register it as a failure [8a]. Exit the loop unless we have specified «all» [8b].

[9] Print the Boolean value.

Running it:

$ ./digit-count-value 1 2 1 0
True

$ ./digit-count-value  0 3 0
False

Looking good.

With verbose mode:

$ ./digit-count-value -v 1 2 1 0
: Bag: (1=>2,0=>1,2=>1).Bag
: @ints[0] = 1, the digit 0 occurs 1 time(s) | OK
: @ints[1] = 2, the digit 1 occurs 2 time(s) | OK
: @ints[2] = 1, the digit 2 occurs 1 time(s) | OK
: @ints[3] = 0, the digit 3 occurs 0 time(s) | OK
True

$ ./digit-count-value -v 0 3 0
: Bag: (3=>1,0=>2).Bag
: @ints[0] = 0, the digit 0 occurs 2 time(s) | Not OK
False

«All» mode does not exit on the first failure, but goes on to the bitter end:

$ ./digit-count-value -a 1 2 1 0
: Bag: (2=>1,1=>2,0=>1).Bag
: @ints[0] = 1, the digit 0 occurs 1 time(s) | OK
: @ints[1] = 2, the digit 1 occurs 2 time(s) | OK
: @ints[2] = 1, the digit 2 occurs 1 time(s) | OK
: @ints[3] = 0, the digit 3 occurs 0 time(s) | OK
True

$ ./digit-count-value -a 0 3 0
: Bag: (3=>1,0=>2).Bag
: @ints[0] = 0, the digit 0 occurs 2 time(s) | Not OK
: @ints[1] = 3, the digit 1 occurs 0 time(s) | Not OK
: @ints[2] = 0, the digit 2 occurs 0 time(s) | OK
False

And that's it.