Relatively Lucky
with Raku

by Arne Sommer

Relatively Lucky with Raku

[305] Published 29. August 2024.

This is my response to The Weekly Challenge #284.

Challenge #284.1: Lucky Integer

You are given an array of integers, @ints.

Write a script to find the lucky integer if found otherwise return -1. If there are more than one then return the largest.

A lucky integer is an integer that has a frequency in the array equal to its value.

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

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

say @ints.Bag.grep({ $_.key == $_.value })>>.key.sort.tail // -1;
########## 2  # 3 ####################### # 4 ### 5 ## 6 # 7 #### 

[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 the frequencies are the values.

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

[3] Then keep only those elemenst where the value equals the frequency.

[4] Extrect the keys (i.e. the original values) from those elements.

[5] Sort the list of values.

[6] Pick the last element in the list, the tail.

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

[7] Empty list? Use «-1» instead.

Running it:

$ ./lucky-integer 2 2 3 4
2

$ ./lucky-integer 1 2 2 3 3 3
3

$ ./lucky-integer 1 1 1 3
-1

Looking good.

No verbose mode for this onelinerish program.

We can actually make it slightly shorter:

File: lucky-integer-kv
#! /usr/bin/env raku

unit sub MAIN (*@ints where all(@ints) ~~ Int && @ints.elems > 0);

say @ints.Bag.grep({ [==] $_.kv })>>.key.sort.tail // -1;
#################### 1 ##########

[1] Use kv to get the key and value as a list, and the Reduction Metaoperator [] with the equality operator == to compare them with each other.

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

See docs.raku.org/language/operators#Reduction_metaoperators for more information about the Reduction Metaoperator [].

Challenge #284.2: Relative Sort

You are given two list of integers, @list1 and @list2. The elements in the @list2 are distinct and also in the @list1.

Write a script to sort the elements in the @list1 such that the relative order of items in @list1 is same as in the @list2. Elements that is missing in @list2 should be placed at the end of @list1 in ascending order.

Example 1:
Input: @list1 = (2, 3, 9, 3, 1, 4, 6, 7, 2, 8, 5)
       @list2 = (2, 1, 4, 3, 5, 6)
Ouput: (2, 2, 1, 4, 3, 3, 5, 6, 7, 8, 9)
Example 2:
Input: @list1 = (3, 3, 4, 6, 2, 4, 2, 1, 3)
       @list2 = (1, 3, 2)
Ouput: (1, 3, 3, 3, 2, 2, 4, 4, 6)
Example 3:
Input: @list1 = (3, 0, 5, 0, 2, 1, 4, 1, 1)
       @list2 = (1, 0, 3, 2)
Ouput: (1, 1, 1, 0, 0, 3, 2, 4, 5)
File: relative-sort
#! /usr/bin/env raku

unit sub MAIN ($list1, $list2);                 # [1]

my @list1 = $list1.words;                       # [1a]
my @list2 = $list2.words;                       # [1b]
my $bag1  = @list1.Bag;                         # [2]
my %seen;                                       # [3]
my @sorted;                                     # [4]

for @list2 -> $element                          # [5]
{
  @sorted.append: $element xx $bag1{$element};  # [5a]
  %seen{$element} = True;                       # [5b]
}

for @list1.sort -> $element                     # [6]
{
  next if %seen{$element};                      # [6a]
  @sorted.push: $element;                       # [6b]
}

say "({ @sorted.join(", ") })";                 # [7]

[1] The two lists, each one as a string of space separated values. Split them into lists, without any form of type or value enforcement [1a,1b].

[2] Turn the first list into a Bag, so that we can extract the frequencies easily.

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

[3] Have we seen the current value already?

[4] The result will end up here.

[5] Iterate over the order list, add the required number (count) (with the list repetition operator xx) of the current value. We use append so that we get them added to the list as individual elements, instead of as one sublist.

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

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

[6] Iterate over the values, ignore already processes values [6a], and add them otherwise [6b] (with push) - one per loop iteration if we have duplicates.

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

[7] Pretty print the result.

Running it:

$ ./relative-sort "2 3 9 3 1 4 6 7 2 8 5" "2 1 4 3 5 6"
(2, 2, 1, 4, 3, 3, 5, 6, 7, 8, 9)

$ ./relative-sort "3 3 4 6 2 4 2 1 3" "1 3 2" 
(1, 333, 22, 4, 4, 6)

$ ./relative-sort "3 0 5 0 2 1 4 1 1" "1 0 3 2"
(1, 1, 1, 0, 0, 3, 2, 4, 5)

Looking good.

No verbose mode, due to aqute laziness...

We can get rid of the second loop (and the %seen hash).

File: relative-sort-kxxv
#! /usr/bin/env raku

unit sub MAIN ($list1, $list2);

my @list1 = $list1.words;
my @list2 = $list2.words;
my $bag1  = @list1.BagHash;       # [1]

my @sorted;

for @list2 -> $element
{
  @sorted.append: $element xx $bag1{$element};

  $bag1{$element}:delete;         # [2]
}

@sorted.append: $bag1.kxxv.sort;  # [3]

say "({ @sorted.join(", ") })";

[1] A BagHash is the read write version of the read only Bag.

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

[2] Remove the current value from the Baghash.

[3] Extract the values from the BagHash, with the aptly named kxxv (that is a kv with a xx added to the mix, or bag as it were) that gives us the correct amount of each value. We append the sorted list to the result.

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

Running it gives the expected result:

$ ./relative-sort-kxxv "2 3 9 3 1 4 6 7 2 8 5" "2 1 4 3 5 6"
(2, 2, 1, 4, 3, 3, 5, 6, 7, 8, 9)

$ ./relative-sort-kxxv "3 3 4 6 2 4 2 1 3" "1 3 2"
(1, 3, 3, 3, 2, 2, 4, 4, 6)

$ ./relative-sort-kxxv "3 0 5 0 2 1 4 1 1" "1 0 3 2"
(1, 1, 1, 0, 0, 3, 2, 4, 5)

And that's it.