This is my response to The Weekly Challenge #217.
n x n
matrix where n >= 2.
3rd smallest
element in the sorted matrix.
Input: @matrix = ([3, 1, 2], [5, 2, 4], [0, 1, 3])
Output: 1
The sorted list of the given matrix: 0, 1, 1, 2, 2, 3, 3, 4, 5.
The 3rd smallest of the sorted list is 1.
Example 2:
Input: @matrix = ([2, 1], [4, 5])
Output: 4
The sorted list of the given matrix: 1, 2, 4, 5.
The 3rd smallest of the sorted list is 4.
Example 3:
Input: @matrix = ([1, 0, 3], [0, 0, 0], [1, 2, 1])
Output: 0
The sorted list of the given matrix: 0, 0, 0, 0, 1, 1, 1, 2, 3.
The 3rd smallest of the sorted list is 0.
We need a way of specifying
a matrix on the command line, and can reuse the syntax I last used in
The Same Toeplitz with Raku. E.g.
"1 2 3 | 4 5 6 | 7 8 9"
which will give three rows (separated
by |
) with tree values each (separated by space(s)).
#! /usr/bin/env raku
unit sub MAIN (Str $matrix = "3 1 2 | 5 2 4 | 0 1 3", :v(:$verbose)); # [1]
my @matrix = $matrix.split("|")>>.words; # [2]
my @size = @matrix>>.elems; # [3]
die "Must have at least 2 rows" unless @size.elems >= 2; # [4]
die "The rows must have the same size" unless [==] @size; # [5]
die "The number of columns and rows must be the same"
unless @size[0] == @size.elems; # [6]
my @sorted = @matrix[*;*].sort; # [7]
say ":Sorted: { @sorted.join(",") }" if $verbose;
say @sorted[2]; # [8]
[1] Note the default matrix.
[2] Split the matrix into rows (with split
), and then each row
into a list of values (with words
). This leaves us with an array
of sequence rows (the Seq
type), which is ok in our case as we
consume them only once (in [7]). We could have added >>.Array
at
the end (before the ;
) to avoid that.
[3] Get the number of elements in each row.
[4] We require at least two rows.
[5] All the rows must have the same number of elements.
[6] The number of rows must be the same as the number of elements in the rows.
[7] Flatten the matrix (a two dimensional array) with the [*;*]
indexer, telling Raku that we want everything from the first and second
dimension, and then sort the values.
[8] The third lowest value resides at index 2 (as the indices start at zero).
Running it:
$ ./sorted-matrix
1
$ ./sorted-matrix "2 1 | 4 5"
4
$ ./sorted-matrix "1 0 3 | 0 0 0 | 1 2 1"
0
Looking good.
With verbose mode:
$ ./sorted-matrix -v
:Sorted: 0,1,1,2,2,3,3,4,5
1
$ ./sorted-matrix -v "2 1 | 4 5"
:Sorted: 1,2,4,5
4
$ ./sorted-matrix -v "1 0 3 | 0 0 0 | 1 2 1"
:Sorted: 0,0,0,0,1,1,1,2,3
0
Constructing a matrix just so that we can deconstruct it right away may seem a little silly, and we can make the program shorter if we skip that phase - and even shorter if we drop all the pesky error checking:
File: sorted-matrix-cheat
#! /usr/bin/env raku
unit sub MAIN (Str $matrix = "3 1 2 | 5 2 4 | 0 1 3");
say $matrix.words.grep( * !~~ /\|/ )>>.Int.sort[2]; # [1]
[1] Turn the string into an array (with words
). Then use
grep
to get rid of the vertical bars. Then we coerce the
values to integers (from the strings returned by words
),
so that sort
will sort them numerically. And finally we
print the value at index 2.
Running it gives the same result, as long as we do not try to invoke verbose mode or use junk values:
$ ./sorted-matrix-cheat
1
$ ./sorted-matrix-cheat "2 1 | 4 5"
4
$ ./sorted-matrix-cheat "1 0 3 | 0 0 0 | 1 2 1"
0
Turning this one into a one liner is easy:
say @*ARGS[0].words.grep( * !~~ /\|/ )>>.Int.sort[2];
Note that this requires the usage of my matrix command line specification format.
Input: @list = (1, 23)
Output: 231
Example 2:
Input: @list = (10, 3, 2)
Output: 3210
Example 3:
Input: @list = (31, 2, 4, 10)
Output: 431210
Example 4:
Input: @list = (5, 11, 4, 1, 2)
Output: 542111
Example 5:
Input: @list = (1, 10)
Output: 110
Sorting the values as strings (and not as numbers, as we did above), and then gluing them together in reverse order should get us there. Perhaps...
File: max-number-leg
#! /usr/bin/env raku
unit sub MAIN (*@list where @list.elems > 0
&& all(@list) ~~ /^<[1..9]><[0..9]>*$/); # [1]
say @list.sort({ $^b leg $^a }).join; # [2]
[1] A slurpy array with the values, all of which must be non-negative integers (starting with 1..9 and followed by zero or more of 0..9).
[2]
Sort the values with this explicit code block; $^b
is mapped to the second argument in each comparison, and $^a
to the first, thus reversing the sort order. The leg
(less
equal greater) operator sorts the values as texts, just as we need.
(The <=>
operator works on numbers, and cmp
(compare) is a smart version that tries to do the right thing. But the
IntStr
type we get courtesy of the command line will confuse
cmp
into sorting them as strings.)
See
docs.raku.org/routine/leg
for more information about the string thre-way comparator leg
.
See
docs.raku.org/routine/<=>
for more information about the Numeric three-way comparator <=>
.
See
docs.raku.org/routine/cmp
for more information about the Generic "smart" three-way comparator
cmp
.
Running it:
$ ./max-number-leg 1 23
231
$ ./max-number-leg 10 3 2
3210
$ ./max-number-leg 31 2 4 10
431210
$ ./max-number-leg 5 11 4 1 2
542111
$ ./max-number-leg 1 10
101
The last one did not come out as expected (i.e. we should have gotten 110
).
The problem is that numeric comparison is correct for all the values, except where we have two value that start out the same - but one of them has one or more zeroes added to the end.
Here is another one that fails to give the correct answer:
$ ./max-number-leg 10 100
10010
We can write a custom sorting function to sort it out (pun intended):
File: max-number-arm
#! /usr/bin/env raku
unit sub MAIN (*@list where @list.elems > 0 && all(@list) ~~ /^<[1..9]><[0..9]>*$/);
say @list.sort(&arm).join; # [1]
sub arm ($a, $b)
{
return False if $b.substr(0, $a.chars) eq $a && $b.substr($a.end +1,1) eq '0';
return True if $a.substr(0, $b.chars) eq $b && $a.substr($b.end +1,1) eq '0';
return $b leg $a;
}
[1] This is how to specify a function as an argument. Without the &
sigil, it would have been executed right away. (The «arm» name is a pun on the
original «leg».)
Running it with the troublesome values:
$ ./max-number-arm 1 10
110
Swapping the values works as well, as does adding zeroes:
$ ./max-number-arm 10 1
110
$ ./max-number-arm 10 1
110
$ ./max-number-arm 10 100
10100
$ ./max-number-arm 100 10
10100
But this does not work:
$ ./max-number-arm 3 31 2
3132
We should have gotten 3312
...
On the other hand, this works out:
$ ./max-number-arm 3 34 2
3432
An illustration may help:
3 comes before 31, but 34 comes before 3...
This is too complicated. Let us do it in another way entirely, leaving the heavy lifting to Raku:
File: max-number
#! /usr/bin/env raku
unit sub MAIN (*@list where @list.elems > 0
&& all(@list) ~~ /^<[1..9]><[0..9]>*$/,
:v(:$verbose));
my @permutations = @list.permutations; # [1]
my @values = @permutations>>.join;
say ":Permutations: ", @permutations if $verbose;
say ":Values: ", @values if $verbose;
say @values.max; # [2]
[1] We have a list of values, and permutations
gives us all the ways we can sort those values. (See the verbose mode ouput for
examples.)
See
docs.raku.org/routine/permutations for more information about permutations
.
[2] Use max
to get the highest numeric value. We passed
them along as strings courtesy of the join
, but max
coerces
them to numbers.
See
docs.raku.org/routine/max
for more information about max
.
Running it, with verbose mode:
$ ./max-number -v 1 23
:Permutations: [(1 23) (23 1)]
:Values: [123 231]
231
$ ./max-number -v 10 3 2
:Permutations: [(10 3 2) (10 2 3) (3 10 2) (3 2 10) (2 10 3) (2 3 10)]
:Values: [1032 1023 3102 3210 2103 2310]
3210
$ ./max-number -v 31 2 4 10
:Permutations: [(31 2 4 10) (31 2 10 4) (31 4 2 10) (31 4 10 2) (31 10 2 4) \
(31 10 4 2) (2 31 4 10) (2 31 10 4) (2 4 31 10) (2 4 10 31) (2 10 31 4) \
(2 10 4 31) (4 31 2 10) (4 31 10 2) (4 2 31 10) (4 2 10 31) (4 10 31 2) \
(4 10 2 31) (10 31 2 4) (10 31 4 2) (10 2 31 4) (10 2 4 31) (10 4 31 2) \
(10 4 2 31)]
:Values: [312410 312104 314210 314102 311024 311042 231410 231104 243110 \
241031 210314 210431 431210 431102 423110 421031 410312 410231 103124 \
103142 102314 102431 104312 104231]
431210
$ ./max-number -v 5 11 4 1 2
:Permutations: [(5 11 4 1 2) (5 11 4 2 1) (5 11 1 4 2) (5 11 1 2 4) \
(5 11 2 4 1) (5 11 2 1 4) (5 4 11 1 2) (5 4 11 2 1) (5 4 1 11 2) \
(5 4 1 2 11) (5 4 2 11 1) (5 4 2 1 11) (5 1 11 4 2) (5 1 11 2 4) \
(5 1 4 11 2) (5 1 4 2 11) (5 1 2 11 4) (5 1 2 4 11) (5 2 11 4 1) \
(5 2 11 1 4) (5 2 4 11 1) (5 2 4 1 11) (5 2 1 11 4) (5 2 1 4 11) \
(11 5 4 1 2) (11 5 4 2 1) (11 5 1 4 2) (11 5 1 2 4) (11 5 2 4 1) \
(11 5 2 1 4) (11 4 5 1 2) (11 4 5 2 1) (11 4 1 5 2) (11 4 1 2 5) \
(11 4 2 5 1) (11 4 2 1 5) (11 1 5 4 2) (11 1 5 2 4) (11 1 4 5 2) \
(11 1 4 2 5) (11 1 2 5 4) (11 1 2 4 5) (11 2 5 4 1) (11 2 5 1 4) \
(11 2 4 5 1) (11 2 4 1 5) (11 2 1 5 4) (11 2 1 4 5) (4 5 11 1 2) \
(4 5 11 2 1) (4 5 1 11 2) (4 5 1 2 11) (4 5 2 11 1) (4 5 2 1 11) \
(4 11 5 1 2) (4 11 5 2 1) (4 11 1 5 2) (4 11 1 2 5) (4 11 2 5 1) \
(4 11 2 1 5) (4 1 5 11 2) (4 1 5 2 11) (4 1 11 5 2) (4 1 11 2 5) \
(4 1 2 5 11) (4 1 2 11 5) (4 2 5 11 1) (4 2 5 1 11) (4 2 11 5 1) \
(4 2 11 1 5) (4 2 1 5 11) (4 2 1 11 5) (1 5 11 4 2) (1 5 11 2 4) \
(1 5 4 11 2) (1 5 4 2 11) (1 5 2 11 4) (1 5 2 4 11) (1 11 5 4 2) \
(1 11 5 2 4) (1 11 4 5 2) (1 11 4 2 5) (1 11 2 5 4) (1 11 2 4 5) \
(1 4 5 11 2) (1 4 5 2 11) (1 4 11 5 2) (1 4 11 2 5) (1 4 2 5 11) \
(1 4 2 11 5) (1 2 5 11 4) (1 2 5 4 11) (1 2 11 5 4) (1 2 11 4 5) \
(1 2 4 5 11) (1 2 4 11 5) (2 5 11 4 1) (2 5 11 1 4) (2 5 4 11 1) \
(2 5 4 1 11) ...]
:Values: [511412 511421 511142 511124 511241 511214 541112 541121 541112 \
541211 542111 542111 511142 511124 514112 514211 512114 512411 521141 \
521114 524111 524111 521114 521411 115412 115421 115142 115124 115241 \
115214 114512 114521 114152 114125 114251 114215 111542 111524 111452 \
111425 111254 111245 112541 112514 112451 112415 112154 112145 451112 \
451121 451112 451211 452111 452111 411512 411521 411152 411125 411251 \
411215 415112 415211 411152 411125 412511 412115 425111 425111 421151 \
421115 421511 421115 151142 151124 154112 154211 152114 152411 111542 \
111524 111452 111425 111254 111245 145112 145211 141152 141125 142511 \
142115 125114 125411 121154 121145 124511 124115 251141 251114 254111 \
254111 ...]
542111
$ ./max-number -v 1 10
:Permutations: [(1 10) (10 1)]
:Values: [110 101]
110
$ ./max-number -v 3 31 2
:Permutations: [(3 31 2) (3 2 31) (31 3 2) (31 2 3) (2 3 31) (2 31 3)]
:Values: [3312 3231 3132 3123 2331 2313]
3312
$ ./max-number -v 3 34 2
:Permutations: [(3 34 2) (3 2 34) (34 3 2) (34 2 3) (2 3 34) (2 34 3)]
:Values: [3342 3234 3432 3423 2334 2343]
3432
Looking good.
We can turn this program into a one liner as well, if we leave out the error handling:
say @*ARGS[0].permutations>>.join.max;
And that's it.