03 May 2024 | Challenge 267 |

# Lines and Signs

## Task 1: Product Sign

**Submitted by: Mohammad Sajid Anwar**

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
```

### Solution

There are some subtleties in this task:

- The integers’ product may become very large and could overflow the floating point range. As we are interested in its sign only, we don’t need to perform any multiplication at all. We just need to flip a sign.
- We can terminate the process once we encounter a zero.
- To be consistent with
`product_sign((-1) x $n) == (-1)**$n`

, the empty list shall have a product sign of`1`

.

Though this looks like a typical use case for `reduce`

, we need to trick it into short-circuit to take advantage of these special circumstances.

```
use strict;
use warnings;
use List::Util 'reduce';
sub product_sign {
eval {reduce {($b || die) < 0 ? -$a : $a} 1, @_} || 0;
}
```

If there is no zero in the list, then `reduce`

either returns minus one or plus one, which is passed thought by `eval`

. This is a true Boolean value that gives the final result.
OTOH, if there is a zero in the list, the `reduce`

block will die, leading to a false return value from `eval`

that is safely transformed into a zero.

See the full solution to task 1.

## Task 2: Line Counts

**Submitted by: Mohammad Sajid Anwar**

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

### Solution

As `@width`

has 26 elements, we have to make sure only lower case letters are processed from `$str`

.
This may be achieved easily with a global match using `/[a-z]/g`

.

Furthermore, the characters `'a'`

to `'z'`

are not contiguous in some encoding schemata, particularly EBCDIC.
Therefore we must not use
`ord($c) - ord('a')`

as an index for `@widths`

.

Finally considering the edge case where `$str`

is empty: `$lines`

and `$width`

should both be zero.
To handle this case smoothly, we separate the line wrap and the line count’s increment by different conditions: If the next character would overflow the current line, we reset the width to zero.
And we increment the line count, whenever we put a character into an empty line.

```
use strict;
use warnings;
use feature qw(state postderef);
use experimental 'signatures';
sub line_counts ($str, $widths, $limit) {
my ($lines, $width) = (0, 0);
state $letters;
$letters->@{'a' .. 'z'} = (0 .. 25) unless $letters;
for my $c ($str =~ /[a-z]/g) {
my $len = $widths->[$letters->{$c}];
if ($width + $len > $limit) {
$width = 0;
}
$lines++ unless $width;
$width += $len;
}
($lines, $width);
}
```

See the full solution to task 2.