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.