# ETOOBUSY š minimal blogging for the impatient

# Autobiographical numbers

**TL;DR**

Letās look at an example of using ConstraintSolver.pm, introduced in previous post More constraint programming.

But firstā¦ a confession: Iām quite *not* satisfied with the code that Iām
going to showā¦ but time is a limited resource, so Iāll stick with it
because it shows how to use ConstraintSolver.pm on a real problem and
gets the job done.

# Autobiographical numbers?

It *sort of* started from video Can you solve the Leonardo da Vinci riddle? - Tanya Khovanova,
but *not really*. Itās *complicated* š

Sure, the name *autobiographical numbers* comes from that video. But full
credits have to be given to the *stellar* Coursera online course
Discrete Optimization, by Professor Pascal Van Hentenryck and Dr.
Carleton Coffrin. Itās taking more time than I anticipated, but itās
totally worth the effort.

In particular, class 6 *CP 6 - redundant constraints, magic series, market
split* in week 3 deals about *magic series*ā¦ which are exactly the same
concept.

So, in a nutshell, assume you have an array of $N$ elements, indexed starting
from `0`

:

```
0 1 2 3 4 n-2 n-1
+---+---+---+---+---+ ... +---+---+
| | | | | | | | |
+---+---+---+---+---+ ... +---+---+
```

Each slot in the array is supposed to contain a non-negative integer number, representing the number of times that the slotās index appears in the array itself. I guess Douglas Richard Hofstadter would feel at home.

Example? Letās consider $N = 4$, this would be a solution:

```
0 1 2 3
+---+---+---+---+
| 1 | 2 | 1 | 0 |
+---+---+---+---+
```

As you can see:

- slot
`0`

contains a $1$, which means that there is exactly one $0$ the array. This is true indeed, itās in slot`3`

; - slot
`1`

contains a $2$, which is consistent with the fact that $1$ appears exactly two times in the array (in slot`0`

and in slot`2`

) - you get the idea for slots
`2`

and`3`

š

# Letās solve this!

Letās use ConstraintSolver.pm then:

```
1 sub autobiographical_numbers ($n) {
2 my $solution = [
3 map {
4 +{map { $_ => 1 } 0 .. $n - 1}
5 } 1 .. $n
6 ];
7 my @constraints = map { main->can('constraint_' . $_) }
8 (qw< basic total_sum weighted_sum last_is_zero >);
9 my $state = solve_by_constraints(
10 constraints => \@constraints,
11 is_done => \&is_done,
12 search_factory => \&explore,
13 start => {solution => $solution},
14 logger => ($ENV{VERBOSE} ? \&printout : undef),
15 );
16 } ## end sub autobiographical_numbers ($n)
```

The function `solve_by_constraints`

does the overall orchestration, but the
heavy lifting is up to us: think about a data structure, as well as
providing suitable callback functions for the different stages of the
constraints programming search.

Our *solution* starts as an Array of Arrays where each slot contains the
*candidates* for the specific slot. Itās initialized with all integers
between $0$ and $N-1$ (both included) because there can be no less than $0$
and no more tha $N-1$, right?

The data structure is a hash reference passed via `start`

(line 13). Itās a
reference to a hash with a few keys inside. Itās initialized with
`solution`

, an array reference containing hashes whose keys are the possible
candidate values to occupy the slot. This choice is probably something that
should be revisited, because it makes it easier to look for specific
elements, but it makes it very bad to e.g. look for the minimum or the
maximum element. As anticipated, Iām not exactly proud of this code!

# Accessories

The logger functions is quite simple:

```
1 sub printout ($phase, $status, $exception = undef) {
2 if ($phase eq 'backtrack') {
3 if ($@) {
4 (my $e = $@) =~ s{\sat\s.*?\sline\s[0-9]+\.\s+\z}{}mxs;
5 $phase = "backtrack[$e]";
6 }
7 else {
8 $phase = 'explore';
9 }
10 }
11 say $phase, ' => ', encode_json [
12 map {
13 my @candidates = sort { $a <=> $b } keys $_->%*;
14 @candidates > 1 ? \@candidates
15 : @candidates > 0 ? 0 + $candidates[0]
16 : '[]'
17 } $status->{solution}->@*
18 ];
19 } ## end sub printout
```

Each hash reference is transformed back to a sorted array before being put
in the output array, whose reference is encoded with json and then printed
out. The logging function receives the *step* as the first argument, so we
print that too; additionally, when backtracking, the function also receives
the exception that was thrown, so we make sure to include it (line 5) or to
mark this as a simple search start (line 8).

Function `is_done`

tells us whether our quest is complete or not:

```
sub is_done ($status) {
return scalar(grep { keys $_->%* > 1 } $status->{solution}->@*) == 0;
}
```

It basically counts the number of slots where we donāt have a decision yet (i.e. where there is more than one alternative), and gives green light only when all slots are decided.

# Exploring

The `search_factory`

function is supposed to give out a sub that iterates
over possible alternative decisions at a specific level in our depth search.
In this case, each level addresses one slot.

```
1 sub explore ($status) {
2 my $solution = dclone($status->{solution}); # our working solution
3 my $n = $solution->@*;
4
5 # this investigates a single slot...
6 my $slot_id = 0;
7 while ($slot_id < $n) {
8 last if keys $solution->[$slot_id]->%* > 1;
9 $slot_id++;
10 }
11 die 'wtf?!?' if $slot_id >= $n;
12 my @candidates = sort { $a <=> $b } keys $solution->[$slot_id]->%*;
13 my $amount;
14 return sub ($status) { # get "next" solution
15 return unless @candidates;
16 $amount = shift @candidates;
17 $status->{solution} = dclone($solution);
18 $status->{solution}[$slot_id] = {$amount => 1};
19 return 1;
20 };
21 } ## end sub explore ($status)
```

This is why line 14 returns a `sub`

. In the preparation phase (lines 2 to
13) we set up for a search in a suitable slot, i.e. a slot where we still
have to take a decision (hence the test in line 8). When this slot is found
in `$slot_id`

, we take all of its alternatives and populate `@candidates`

,
which we will use for iterating different alternatives *inside* the iterator
function.

At each step, we will first of all restore the status as in the beginning
(line 17, to *undo* any pruning that was attempted in the previous step),
then set the specific slot to a hash that has one single choice among the
candidates (line 18).

# Curious?

Weāre at the end of this post nowā¦ if youāre curious about the constraints, please hold on until the next post, or take a sneak peek at the repository.

**Update** fix code with latest changes, and add reference to repository.

# So longā¦

Curious about the whole series? Here it is:

- Autobiographical numbers
- Autobiographical numbers constraints - basic
- Autobiographical numbers constraints - last is zero
- Autobiographical numbers constraints - weighted sum
- Autobiographical numbers constraints - luckier weighted sum
- Autobiographical numbers - step up
- Code repository

Comments? Please comment below!

*Comments? Octodon, Twitter, GitHub, Reddit, or drop me a line!*