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.