Seven Angry Slices
with Raku

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

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.