ETOOBUSY 🚀 minimal blogging for the impatient
Fibonacci Sum part 2
TL;DR
Let’s complete the discussion about the solution to the Perl Weekly Challenge #077.
In Fibonacci Sum part 1 we laid the foundations for the solution to the Challenge 077 with a function that gives us the Zeckendorf’s decomposition of a positive integer. It’s now time to address the other half of the problem, i.e. finding all possible arrangements.
Zeckendorf’s decomposition is minimal in the sense that it provides the biggest (and fewest) Fibonacci numbers that fit the request. Hence, every other solution can be built from it, by trying to further decompose each of those Fibonacci numbers into constituents, remembering that we cannot have duplicates in any of the correct solutions.
Let’s consider one of the examples, i.e. decomposing 9
. The minimal
greedy solution yields us with:
1 + 8 = 9
Now, 1
is the lowest of our Fibonacci Numbers and cannot be decomposed
further, so we can try with 8
:
1 + 3 + 5 = 9
Can we go further? Actually… no, because 3
cannot be decomposed (it
would yield a duplicate 1
) and 5
cannot be decomposed (it would
yield a duplicate 3
). So well… we’re done.
What if we started at 22 instead?
1 + 21 = 22
1 + 8 + 13 = 22
1 + 3 + 5 + 13 = 22
Again… we’re done. After struggling a bit, it’s easy to see that decomposing a Fibonacci number leads to a linear ladder of possibilities and does not explode, because only the lowest constituent can be decomposed (if any).
Too complicated? Let’s look at 21
in our example; the immediate
decomposition is into its two predecessors:
8 + 13 = 21
It’s easy to see that we cannot decompose 13
further, because it would
yield to a duplicate 8
. Maybe in some future round of
decomposition…? No again, because at that point the decompositions of
the 8
that we see here and the 8
needed for the 13
would compete
against each other and lead to a duplicate.
So, at any round, we’re left with decomposing the lowest number in the sequence, which simplifies things a lot:
3 + 5 + 13 = 21
1 + 2 + 5 + 13 = 21
and we’re done.
Now, in the original decomposition of 22
, it’s easy to see that the
last step is not useful, because we already have a 1
. This happense
any time we hit a lower number in Zeckendorf’s
decomposition by decomposing a higher one:
8 + 34 = 42
8 + 13 + 21 = 42
We cannot go further with the 13
because it would need a duplicate
8
or a duplicate 5
(constituent of the other 8
, so we’re done.
With this in mind, here’s how we will address the whole thing:
- compute the Zeckendorf’s decomposition of the input number
- starting from the bigger constituent in the decomposition, compute all its alternative forms by breaking up its lowest constituent, up to the immediately following number in the decomposition of the original number
- try to mix and match all possible arrangements of alternatives for the Fibonacci numbers that make up the target integer.
Sounds complicated? It is!
Let’s start simple. Say that we have number 44
, that is:
2 + 8 + 34 = 44
Now we find all possible alternative representations for these three
consitituents, cutting out branches that would yield a duplicate. We
start from 34
and break it up until we hit 8
(actually, we hit the
number before 8
)
34
21 + 13
We can do the same with the 8
, stopping at the 2
or the immediately
previous one:
8
3 + 5
The following function finds all possible alternatives for a Fibonacci number, going down the ladder but only up to a certain point:
1 sub alternatives {
2 my ($i, $il) = @_;
3 my @item = ($i);
4 my @retval = ([$i]);
5 while ($i > $il + 1) {
6 pop @item;
7 push @item, $i - 1, $i - 2;
8 push @retval, [@item];
9 $i -= 2;
10 }
11 return \@retval;
12 }
Again, we’re working with indexes into an array of Fibonacci numbers,
but you get the idea. Starting from index $i
, we go back and always
decompose (line 7) only the lowest constituent (line 6), until we hit a
rock bottom provided by a “lowest index” $il
(line 5). This new
alternative is saved (line 8) and then we go back by two indexes (line
9) because we already used those Fibonacci numbers in line 7.
We’re now ready to look at the main
sub:
1 sub main {
2 my ($n) = @_;
3
4 # compute the "basic" Zeckendorf decomposition of $n
5 my $lk = lekkerkerker($n);
6
7 # compute a "reasonable" decomposition into possible non-overlapping
8 # components
9 my @components;
10 for my $i (reverse 0 .. $#{$lk->{indexes}}) {
11 my $index = $lk->{indexes}[$i];
12 my $low_index = $i ? $lk->{indexes}[$i - 1] : 0;
13 my $alts = alternatives($index, $low_index);
14 push @components, $alts;
15 }
16
17 # compute all possible arrangements, reject those with overlaps and
18 # print the others
19 nested_loops_recursive(
20 \@components,
21 sub {
22 my @lineup;
23 my %seen;
24 my $sum = 0;
25 for my $constituent (@_) {
26 for my $i (@$constituent) {
27 return if $seen{$i}++;
28 my $fi = $lk->{fibo}[$i];
29 push @lineup, $fi;
30 $sum += $fi;
31 }
32 }
33 die "sum mismatch ($sum vs $n)\n" unless $n == $sum;
34 my $lineup = join ' + ', sort {$a <=> $b} @lineup;
35 print {*STDOUT} "$lineup = $sum\n";
36 }
37 );
38 }
Line 5 calculates the Zeckendorf’s decomposition, no big deal.
Lines 9 through 15 put in @components
all possible representations of
each Fibonacci number in the decomposition, by calling alternatives
and providing the right extremes. Note that the lowest Fibonacci number
in the representation an go down to the bottom of the Fibonacci ladder,
so it gets a lower index of 0 (line 12).
At this point, we have that @components
is an array of arrays of…
arrays, which we want to iterate over. Wait a minute… we can reuse
what we discussed in A simplified recursive implementation of NestedLoops!
This happens in line 19 (note: we got rid of the $opts
parameter): we
provide a reference to @components
and a callback sub to analyze a
particular input arrangement (lines 21 to 36).
This sub is quite… boring: we make sure to avoid duplications (using
the %seen
hash to bail out if some number appears twice, line 27),
record all constituents and make the sum again just to double check.
If we make it to line 34… yay, we have a good arrangement and we can print it out as requested!
So… I guess it’s all from me. If you want to take a look at the whole solution, it’s available here. Have a good one!