Every Product Counts
with Raku

by Arne Sommer

Every Product Counts with Raku

[287] Published 3. May 2024.

This is my response to The Weekly Challenge #267.

Challenge #267.1: Product Sign

You are given an array of @ints.

Write a script to find the sign of product of all integers in the given array. The sign is 1 if the product is positive, -1 if the product is negative and 0 if product is zero.

Example 1:
Input: @ints = (-1, -2, -3, -4, 3, 2, 1)
Output: 1

The product -1 x -2 x -3 x -4 x 3 x 2 x 1 => 144 > 0
Example 2:
Input: @ints = (1, 2, 0, -2, -1)
Output: 0

The product 1 x 2 x 0 x -2 x -1 => 0
Example 3:
Input: @ints = (-1, -1, 1, -1, 2)
Output: -1

The product -1 x -1 x 1 x -1 x 2 => -2 < 0
File: product-sign
#! /usr/bin/env perl6

unit sub MAIN (*@ints where all(@ints) ~~ Int && @ints.elems > 0,  # [1]
               :v(:$verbose));

my $product = [*] @ints;                                           # [2]

say ": Product: $product" if $verbose;

# say $product ?? ( $product / $product.abs ) !! 0;                # [3]

say + ($product <=> 0);                                            # [4]

[1] A slurpy array to catch at leason one integer.

[2] Use the Reduction Metaoperator [] in combination with * (multiplication) to multiply all the values together.

See docs.raku.org/language/operators#Reduction_metaoperators for more information about the Reduction Metaoperator [].

[3] A first try could be something like this; divide the resulting sum by the absolute value of the sum (i.e. without the sign, if any). This will give the expected result. Except when we try to divide by zero. Thus we special case that.

[4] But it is neater to use the three way numerical comparison operator <=>. This operator returns «Same», «Less» or «Equal», which will not do. But we can coerce those values to «-1», «0» and «1» with the Numeric Coercion Prefix +.

See docs.raku.org/routine/<=> for more information about the Numeric three-way comparator <=>.

See docs.raku.org/routine/+ for more information about the Numeric Coercion Prefix Operator +.

Running it:

$ ./product-sign -- -1 -2 -3 -4 3 2 1
1

$ ./product-sign 1 2 0 -2 -1
0

$ ./product-sign -- -1 -1 1 -1 2
-1

Looking good.

With verbose mode:

$ ./product-sign -v -- -1 -2 -3 -4 3 2 1
: Product: 144
1

$ ./product-sign -v 1 2 0 -2 -1
: Product: 0
0

$ ./product-sign -v -- -1 -1 1 -1 2
: Product: -2
-1

Challenge #267.2: Line Counts

You are given a string, $str, and a 26-items array @widths containing the width of each character from a to z.

Write a script to find out the number of lines and the width of the last line needed to display the given string, assuming you can only fit 100 width units on a line.

Example 1:
Input: $str = "abcdefghijklmnopqrstuvwxyz"
              @widths = (10,10,10,10,10,10,10,10,10,10,10,10,10,
                         10,10,10,10,10,10,10,10,10,10,10,10,10)
Output: (3, 60)

Line 1: abcdefghij (100 pixels)
Line 2: klmnopqrst (100 pixels)
Line 3: uvwxyz (60 pixels)
Example 2:
Input: $str = "bbbcccdddaaa"
       @widths = (4,10,10,10,10,10,10,10,10,10,10,10,10,
                  10,10,10,10,10,10,10,10,10,10,10,10,10)
Output: (2, 4)

Line 1: bbbcccdddaa (98 pixels)
Line 2: a (4 pixels)
File: line-counts
#! /usr/bin/env perl6

unit sub MAIN ($str,
               *@widths where all(@widths) ~~ UInt  # [1]
                 && @widths.elems == 26,            # [1a]
               :v(:$verbose));

my %width;                                          # [2]

for 'a' .. 'z' -> $letter                           # [3]
{
  %width{$letter} = @widths.shift.Int;              # [3a]
}

say ": Widths: { %width.raku }" if $verbose;

my $line  = "";                                     # [4]
my $width = 0;                                      # [5]
my $id    = 0;                                      # [6]

for $str.comb -> $letter                            # [7]
{
  if $width + %width{$letter} > 100                 # [8]
  {
    $id++;                                          # [8a]
    say ": Line $id: $line ($width pixels)" if $verbose;
    $line  = "";                                    # [8b]
    $width = 0;                                     # [8c]
  }

  $line  ~= $letter;                                # [9]
  $width += %width{$letter};                        # [10]                       
}

if $line                                            # [11]
{
  $id++;                                            # [12]
  say ": Line $id: $line ($width pixels)" if $verbose;
}

say "($id, $width)";                                # [13]

[1] Ensure exactly 26 unsigned integers.

[2] We are going to calculate the width of all the characters, for easier lookup later on.

[3] Iterate over the letters, settng up the width of each one by shifting off the first (leftmost) remaining width value.

[4] The line is initially empty.

[5] The width of an empty line is zero.

[6] The line counter (or ID).

[7] Iterate over each letter in the input string, with comb.

See docs.raku.org/routine/comb for more information about comb.

[8] Will the length of the current letter exceed the limit if we were to add it to the line? If so, start a new line (by increasing the counter in [8a], by resetting the line itself in [8b] and resetting the line width in [8b]).

[9] Add the current letter to the line (as it will not exceed the limit).

[10] And add the width of that letter to the total width.

[11] After the loop, check if we have any unfinished lines.

[12] If so, increase the line counter.

[13] Print the number of lines, and the current width (i.e. the width of the last line).

Running it:

$ ./line-counts abcdefghijklmnopqrstuvwxyz 10 10 10 10 10 10 10 10 10 \
     10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
(3, 60)

$ ./line-counts -v bbbcccdddaaa 4 10 10 10 10 10 10 10 10 10 10 10 10 10 \
     10 10 10 10 10 10 10 10 10 10 10 10
(2, 4)

Looking good.

With verbose mode:

$ ./line-counts -v abcdefghijklmnopqrstuvwxyz 10 10 10 10 10 10 10 10 10 \
     10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
: Widths: {:a(10), :b(10), :c(10), :d(10), :e(10), :f(10), :g(10), :h(10),
  :i(10), :j(10), :k(10), :l(10), :m(10), :n(10), :o(10), :p(10), :q(10),
  :r(10), :s(10), :t(10), :u(10), :v(10), :w(10), :x(10), :y(10), :z(10)}
: Line 1: abcdefghij (100 pixels)
: Line 2: klmnopqrst (100 pixels)
: Line 3: uvwxyz (60 pixels)
(3, 60)

$ ./line-counts -v bbbcccdddaaa 4 10 10 10 10 10 10 10 10 10 10 10 10 10 \
     10 10 10 10 10 10 10 10 10 10 10 10
: Widths: {:a(4), :b(10), :c(10), :d(10), :e(10), :f(10), :g(10), :h(10), \
  :i(10), :j(10), :k(10), :l(10), :m(10), :n(10), :o(10), :p(10), :q(10), \
  :r(10), :s(10), :t(10), :u(10), :v(10), :w(10), :x(10), :y(10), :z(10)}
: Line 1: bbbcccdddaa (98 pixels)
: Line 2: a (4 pixels)
(2, 4)

And that's it.