This is my response to The Weekly Challenge #200.
Arithmetic Slices
for the given array of
integers.
Input: @array = (1,2,3,4)
Output: (1,2,3), (2,3,4), (1,2,3,4)
Example 2:
Input: @array = (2)
Output: () as no slice found.
File: arithmetic-slices
#! /usr/bin/env raku
unit sub MAIN (*@array where @array.elems && all(@array) ~~ /^<[0..9]>*$/,
:v(:$verbose)); # [1]
my @slices; # [2]
for 0 .. @array.end - 2 -> $i # [3]
{
for $i + 2 .. @array.end -> $j # [4]
{
my $is-arislice = is-arislice(@array[$i..$j]); # [5]
say ": \@array[$i, $j] -> @array[$i..$j] \
{ " -> arithmetic slice" if $is-arislice }" if $verbose;
@slices.push: "({ @array[$i..$j].join(",") })" if $is-arislice; # [5a]
last unless $is-arislice; # [5b]
}
}
say @slices ?? @slices.join(", ") !! "()"; # [6]
sub is-arislice(@array) # [5a]
{
my $diff = @array[1] - @array[0]; # [7]
for 1 .. @array.end -1 -> $i # [8]
{
return False unless @array[$i+1] - @array[$i] == $diff; # [9]
}
return True; # [10]
}
[1] Ensure at least one element (with .elems
), and ensure that
they all (with all
) are non-negative integers. The challende does
not exclude negative integers, but I have chosen to do so.
[2] The slices will end up here, as strings (one for each slice) to make the final output (in [6]) easier.
[3] We do this the hard way, by selecting all possible slices, starting with
the start (so to speak), as an index. As the slice must have at least three
elements, we stop 2 elements short off the end (as given by end
)
so that we can have those three elements.
[4] The second loop, giving the end of the slice, starts 2 elements after the current starting element - so that we have at least three elements.
[5] Then we ask the «is-arislice» procedure to decide if we have an Artithmetic Slice. If we do, we add the slice (as a text) to the slice array ([2]) [5a]. if not, we skip the rest of the inner loop as adding more elements to the end of a slice that is not an Artithmetic Slice will not help in any way [5b].
[6] Print the slices. Note the ternary if used to print a pair of empty parens if we do not have any pairs, as the second example requires it.
[7] The difference between any two neighbouring elements. We start with the first two.
[8] Then we iterate over the indices, from 1 (skipping the first one) to the last but one,
[9] Compare the difference between the values found at the current index and the very next one, with the canonical value (from [7]). We return False (as it is not an Arithmetic Slice) if the values differ.
[10] If we get here, we have not falsified the slice. Then it has to be an Artithmetic Slice.
Running it:
$ ./arithmetic-slices 1 2 3 4
(1,2,3), (1,2,3,4), (2,3,4)
$ ./arithmetic-slices 2
()
Looking good.
Let us have a go at verbose mode, with som additional arrays while we are at it:
$ ./arithmetic-slices -v 1 2 3 4 66
: @array[0, 2] -> 1 2 3 -> arithmetic slice
: @array[0, 3] -> 1 2 3 4 -> arithmetic slice
: @array[0, 4] -> 1 2 3 4 66
: @array[1, 3] -> 2 3 4 -> arithmetic slice
: @array[1, 4] -> 2 3 4 66
: @array[2, 4] -> 3 4 66
(1,2,3), (1,2,3,4), (2,3,4)
$ ./arithmetic-slices -v 2
()
$ ./arithmetic-slices -v 1 2 3 4 66 99
: @array[0, 2] -> 1 2 3 -> arithmetic slice
: @array[0, 3] -> 1 2 3 4 -> arithmetic slice
: @array[0, 4] -> 1 2 3 4 66
: @array[0, 5] -> 1 2 3 4 66 99
: @array[1, 3] -> 2 3 4 -> arithmetic slice
: @array[1, 4] -> 2 3 4 66
: @array[1, 5] -> 2 3 4 66 99
: @array[2, 4] -> 3 4 66
: @array[2, 5] -> 3 4 66 99
: @array[3, 5] -> 4 66 99
(1,2,3), (1,2,3,4), (2,3,4)
$ ./arithmetic-slices -v 1 2 3 4 66 99
: @array[0, 2] -> 1 2 3 -> arithmetic slice
: @array[0, 3] -> 1 2 3 4 -> arithmetic slice
: @array[0, 4] -> 1 2 3 4 66
: @array[1, 3] -> 2 3 4 -> arithmetic slice
: @array[1, 4] -> 2 3 4 66
: @array[2, 4] -> 3 4 66
: @array[3, 5] -> 4 66 99
(1,2,3), (1,2,3,4), (2,3,4)
$ ./arithmetic-slices -v 1 2 31 4 66 99
: @array[0, 2] -> 1 2 31
: @array[1, 3] -> 2 31 4
: @array[2, 4] -> 31 4 66
: @array[3, 5] -> 4 66 99
The slices can overlap, even when the difference is different:
$ ./arithmetic-slices -v 2 4 6 7 8
: @array[0, 2] -> 2 4 6 -> arithmetic slice
: @array[0, 3] -> 2 4 6 7
: @array[1, 3] -> 4 6 7
: @array[2, 4] -> 6 7 8 -> arithmetic slice
(2,4,6), (6,7,8)
a
' through 'g
' as shown (modifed
by yours truly as ascii art):
aaaaaaa
f b
f b
ggggggg
e c
e c
ddddddd
The encoding of each digit can thus be represented compactly as a truth table:
my @truth = qw<abcdef bc abdeg abcdg bcfg acdfg acdefg abc abcdefg abcfg>;
For example, $truth[1] = ‘bc’. The digit 1 would have segments ‘b’ and ‘c’ enabled.
------- ------- -------
| | | | |
| | | | |
-------
| | | | |
| | | | |
------- ------- -------
To qualify as a seven segment display, each segment must be drawn (or not drawn) according
to your @truth table.
200
" was of course chosen to celebrate our 200th
week!
#! /usr/bin/env raku
unit sub MAIN (UInt $int = 200, :v(:$verbose)); # [0]
my @truth = qw<abcdef bc abdeg abcdg bcfg acdfg acdefg abc abcdefg abcfg>
# [1]
my @result; # [2]
for $int.comb -> $digit # [3]
{
say ":Digit: $digit -> @truth[$digit]" if $verbose;
my @matrix; # [4]
# (^7).map({ @matrix[$_] = (" " xx 7).Array; }); # [5]
(^7).map({ @matrix[$_] = " ".comb.Array; }); # [5a]
@matrix[0] = ("-" xx 7).Array if @truth[$digit] ~~ /a/; # [6A]
@matrix[3] = ("-" xx 7).Array if @truth[$digit] ~~ /g/; # [6G]
@matrix[6] = ("-" xx 7).Array if @truth[$digit] ~~ /d/; # [6D]
@matrix[1][0] = "|" if @truth[$digit] ~~ /f/; # [6F]
@matrix[2][0] = "|" if @truth[$digit] ~~ /f/; # [6F]
@matrix[1][6] = "|" if @truth[$digit] ~~ /b/; # [6B]
@matrix[2][6] = "|" if @truth[$digit] ~~ /b/; # [6B]
@matrix[4][0] = "|" if @truth[$digit] ~~ /e/; # [6E]
@matrix[5][0] = "|" if @truth[$digit] ~~ /e/; # [6E]
@matrix[4][6] = "|" if @truth[$digit] ~~ /c/; # [6C]
@matrix[5][6] = "|" if @truth[$digit] ~~ /c/; # [6C]
(^7).map({ ": " ~ @matrix[$_].join })>>.say if $verbose;
(^7).map({ @result[$_].push: @matrix[$_].join }); # [7]
}
(^7).map({ @result[$_].join(" ") })>>.say; # [8]
[0] The UInt
(Unsigned Int) ensures that we avoid a
potential minus sign. The display (matrix) is able to handle it, but the challenge
does not mention it. (The array lookup in [1] will not work in that case, but a hash
would do the trick - if we add a rule for the minus sign.)
See
docs.raku.org/type/UInt for
more information about the Uint
type.
[1] Taken from the challenge text.
[2] The result will end up here, as 7 rows of characters to print.
[3] Iterate over the individual digits.
[4] We build up a 7x7 matrix for each digit, in this variable.
[5] Initialize the matrix with spaces. Both versions ([5] and
[5a]) have the same end result. Note the postfix .Array
, so that we
we change the values later on.
See
docs.raku.org/routine/Array for more
information about the Array
method.
[6] Set the rows or columns given by the a-g letters for the given digit.
[7] Add each row (as a single string for each one) to the result array (from [2]).
[8] Print the result, line by line. The >>.say
adds a newline after
each line.
Running it:
$ ./ss200 200
------- ------- -------
| | | | |
| | | | |
-------
| | | | |
| | | | |
------- ------- -------
Ok.
Let us check all the digits:
$ ./ss200 01234567
------- ------- ------- ------- ------- -------
| | | | | | | | | |
| | | | | | | | | |
------- ------- ------- ------- -------
| | | | | | | | | |
| | | | | | | | | |
------- ------- ------- ------- -------
$ ./ss200 89
------- -------
| | | |
| | | |
------- -------
| | |
| | |
-------
Ok again.
Verbose mode shows each digit in a very verbose way:
$ ./ss200 -v 123
:Digit: 1 -> bc
:
: |
: |
:
: |
: |
:
:Digit: 2 -> abdeg
: -------
: |
: |
: -------
: |
: |
: -------
:Digit: 3 -> abcdg
: -------
: |
: |
: -------
: |
: |
: -------
------- -------
| | |
| | |
------- -------
| | |
| | |
------- -------
It is easy to add support for negative integers, as partially discussed in [0]:
File: ss200-minus
#! /usr/bin/env raku
unit sub MAIN (UInt $int = 200, :v(:$verbose), :n(:$negative)); # [1]
my %truth = ( 0 => 'abcdef', # [2]
1 => 'bc', 2 => 'abdeg', 3 => 'abcdg',
4 => 'bcfg', 5 => 'acdfg', 6 => 'acdefg',
7 => 'abc', 8 => 'abcdefg', 9 => 'abcfg',
'-' => 'g');
my @result;
for $negative ?? "-$int".comb !! $int.comb -> $digit # [3]
{
die "Illegal input $digit" unless %truth{$digit}; # [4]
say ":Digit: $digit -> %truth{$digit}" if $verbose;
my @matrix;
# (^7).map({ @matrix[$_] = (" " xx 7).Array; });
(^7).map({ @matrix[$_] = " ".comb.Array; });
@matrix[0] = ("-" xx 7).Array if %truth{$digit} ~~ /a/;
@matrix[3] = ("-" xx 7).Array if %truth{$digit} ~~ /g/;
@matrix[6] = ("-" xx 7).Array if %truth{$digit} ~~ /d/;
@matrix[1][0] = "|" if %truth{$digit} ~~ /f/;
@matrix[2][0] = "|" if %truth{$digit} ~~ /f/;
@matrix[1][6] = "|" if %truth{$digit} ~~ /b/;
@matrix[2][6] = "|" if %truth{$digit} ~~ /b/;
@matrix[4][0] = "|" if %truth{$digit} ~~ /e/;
@matrix[5][0] = "|" if %truth{$digit} ~~ /e/;
@matrix[4][6] = "|" if %truth{$digit} ~~ /c/;
@matrix[5][6] = "|" if %truth{$digit} ~~ /c/;
(^7).map({ ": " ~ @matrix[$_].join })>>.say if $verbose;
(^7).map({ @result[$_].push: @matrix[$_].join });
}
(^7).map({ @result[$_].join(" ") })>>.say;
[1] The Int
type does not cope with negative integers specified on
the command line, so use the «-n» (negative) command line option (workaround)
instead.
[2] As a hash this time. Note the mapping forthe minus sign at the end.
[3] Iterate over the individual digits, possible prefixed with a minus sign.
[4] An extra sanity check. This allows for the addition of additional signs (letters) at a later date, but for now does not actually matter.
Running it:
$ ./ss200-minus -n 200
------- ------- -------
| | | | |
| | | | |
------- -------
| | | | |
| | | | |
------- ------- -------
[The main image is from Bordeaux (in France), where the trams come in 7 slices…]
And that's it.