by Arne Sommer

# Seven Angry Slices with Raku

[220] Published 22. January 2023.

This is my response to The Weekly Challenge #200.

## Challenge #200.1: Arithmetic Slices

You are given an array of integers.

Write a script to find out all Arithmetic Slices for the given array of integers.

An integer array is called arithmetic if it has at least 3 elements and the differences between any three consecutive elements are the same.

Example 1:
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)

## Challenge #200.2: Seven Segment 200

A seven segment display is an electronic component, usually used to display digits. The segments are labeled '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.

Write a program that accepts any decimal number and draws that number as a horizontal sequence of ASCII seven segment displays, similar to the following:
-------  -------  -------
|  |     |  |     |
|  |     |  |     |
-------
|        |     |  |     |
|        |     |  |     |
-------  -------  -------
To qualify as a seven segment display, each segment must be drawn (or not drawn) according to your @truth table.

The number "200" was of course chosen to celebrate our 200th week!
File: ss200
#! /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.)

[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.

[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.