Pragmatic Bookshelf Brain Teaser
Saw a challenge from the Pragmatic Bookshelf on Mastodon
Fill each column with the digits 1 through 6 without repeating or omitting digits so the sums in the right column are the total of each row. A digit may appear more than one across rows.
Because putting that matrix into alt-text is weird, here’s a table!
| C1 | C2 | C3 | C4 | C5 | TOTAL | 
|---|---|---|---|---|---|
| 6 | 5 | 2 | 19 | ||
| 1 | 3 | 10 | |||
| 1 | 3 | 11 | |||
| 4 | 5 | 24 | |||
| 3 | 2 | 4 | 18 | ||
| 6 | 5 | 4 | 23 | 
Let’s Talk About It
This …
I suppose I have to say it.
This looks like a job for Recursion!
We’re placing a digit into a place in a matrix, proceeding, then testing at the end. We’re returning arrayrefs, and if we have the answer, it’ll be a full one. If not, [].
I expand my habitual use of List::Util, adding first and any to my bag of tricks. any is a boolean, checking an array for anything. Here, any { /x/ } @flatten tests if any of the elements in @flatten contain the letter x. Within my code, I’m using x as the placeholder for unfilled elements, so we can tell if the matrix has unfilled spots.
We then fill the spots in the array, and I use first to find the indexes we need to fill, with $row = first { 'x' eq $matrix->[$_][$col] } 1 .. 6.
We’re supposed to have each column use the numbers 1 through 6, so I use hashes and grep to discern the numbers in each column not already used, and then only use those numbers to fill in the column. Because of this, I don’t have to test if the column are correct, because they can’t not be. I test by sum, which is kinda halfhearted.
I feel I should mention that you don’t need to make a program to solve this. After being hung up a bit, I put the numbers into a spreadsheet, used a few =SUM() lines because I didn’t want to do simple addition if I could get the computer to do it, and was able to discern the numbers by hand, then use that as a test set to make sure my recursion was working.
Show Me The Code!
#!/usr/bin/env perl
use strict;
use warnings;
use experimental qw{ say signatures state fc };
use List::Util qw{ any first sum0 };
my $array = [
    [qw{ 6 x 5 2 x }],
    [qw{ 1 x 3 x x }],
    [qw{ x x 1 3 x }],
    [qw{ 4 5 x x x }],
    [qw{ 3 2 x 4 x }],
    [qw{ x 6 x 6 4 }],
];
my @totals = qw{19 10 11 24 18 23};
my $matrix = fill_matrix( $array, \@totals );
say 'OUTPUT';
say join "\n", map { join ' ', $_->@* } $matrix->@*;
exit;
sub fill_matrix ( $matrix, $totals, $col = 0 ) {
    my @flat = map { $_->@* } $matrix->@*;
    if ( any { $_ eq 'x' } @flat ) {
        no warnings;
        my @column = map { $_->[$col] } $matrix->@*;
        if ( !any { /x/ } @column ) {
            return fill_matrix( $matrix, $totals, $col + 1 );
        }
        my $row = first { 'x' eq $matrix->[$_][$col] } 1 .. 6;
        my %list;
        my @list = 1 .. 6;
        $list{$_}++ for @column;
        my @needed = grep { !$list{$_} } @list;
        my $copy;
        for my $i ( 0 .. -1 + scalar $matrix->@* ) {
            my @row = $matrix->[$i]->@*;
            push $copy->@*, \@row;
        }
        for my $n (@needed) {
            $copy->[$row][$col] = $n;
            my $return = fill_matrix( $copy, $totals, $col );
            return $return if scalar $return->@*;
        }
        return [];
    }
    else {
        for my $i ( 0 .. -1 + scalar $matrix->[0]->@* ) {
            my @col = map { $matrix->[$_][$i] } 0 .. 5;
            my $sum = sum0 @col;
            return [] if $sum != 21;
        }
        for my $i ( 0 .. 5 ) {
            my $t   = $totals->[$i];
            my $sum = sum0 $matrix->[$i]->@*;
            if ( $sum ne $totals->[$i] ) {
                return [];
            }
        }
        return $matrix;
    }
}
$ ./prag_array.pl
OUTPUT
6 1 5 2 5
1 3 3 1 2
2 4 1 3 1
4 5 4 5 6
3 2 6 4 3
5 6 2 6 4
