This is my response to The Weekly Challenge #284.
@ints.
-1.
If there are more than one then return the largest.
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
#! /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 [].
@list1 and @list2. The
elements in the @list2 are distinct and also in the @list1.
@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.
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)
#! /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).
#! /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.