ETOOBUSY 🚀 minimal blogging for the impatient
PWC144 - Ulam Sequence
TL;DR
On with TASK #2 from The Weekly Challenge #144. Enjoy!
The challenge
You are given two positive numbers,
$u
and$v
.Write a script to generate
Ulam Sequence
having at least 10Ulam numbers
where$u
and$v
are the first 2Ulam numbers
.For more information about
Ulam
Sequence, please checkout the website.The standard Ulam sequence (the (1, 2)-Ulam sequence) starts with U1 = 1 and U2 = 2. Then for n > 2, Un is defined to be the smallest integer that is the sum of two distinct earlier terms in exactly one way and larger than all earlier terms.
Example 1
Input: $u = 1, $v = 2 Output: 1, 2, 3, 4, 6, 8, 11, 13, 16, 18
Example 2
Input: $u = 2, $v = 3 Output: 2, 3, 5, 7, 8, 9, 13, 14, 18, 19
Example 3
Input: $u = 2, $v = 5 Output: 2, 5, 7, 9, 11, 12, 13, 15, 19, 23
The questions
It seems that our fine host decided that enough is enough with these questions and discovered the joys of delegation. Very well played, Mr. Anwar, very well played indeed…
The solution
OK, let’s start with Perl. In one word: iterator.
#!/usr/bin/env perl
use v5.24;
use warnings;
use experimental 'signatures';
no warnings 'experimental::signatures';
sub ulam_iterator ($v, $w) {
my @items = ($v, $w);
my $n = 0;
return sub {
ITEM:
while ($n > $#items) {
my %count;
for my $i (0 .. $#items - 1) {
for my $j (reverse($i + 1 .. $#items)) {
my $sum = $items[$i] + $items[$j];
last if $sum <= $items[-1];
$count{$sum}++;
}
}
for my $new (sort { $a <=> $b } keys %count) {
next unless $count{$new} == 1;
push @items, $new;
next ITEM;
}
}
return $items[$n++];
};
}
my @seeds = @ARGV == 2 ? @ARGV : (1, 2);
my $it = ulam_iterator(@seeds);
say join ', ', map { $it->() } 1 .. 10;
The ulam_iterator
function returns… an iterator function. I guess
you saw it coming.
At each call, we generate (if needed) a new element until we have enough, and return the first that was not returned in a previous round. Adding one new item implies:
- finding the number of ways a sum can appear
- isolating the minimum of all sums that appear only once.
For the Raku counterpart we reuse a big chunk of the implementation
BUT using a proper Class
instead of an anonymous function.
#!/usr/bin/env raku
use v6;
class Ulam {
has @!items is built;
has $!n = 0;
method new ($v, $w) { self.bless(items => [$v, $w]) }
method next () {
ITEM:
while $!n > @!items.end {
@!items.push: (@!items X @!items).grep({ $_[0] < $_[1] })
.map({$_.sum}).grep({$_ > @!items[*-1]})
.Bag.pairs.grep({.value == 1}).map({.key}).min;
}
return @!items[$!n++];
}
}
sub MAIN (*@args) {
my ($v, $w) = @args.elems == 2 ?? @args !! (1, 2);
my $ulam = Ulam.new($v, $w);
(1..10).map({$ulam.next}).join(', ').put;
}
There are a lot of tools this time:
- we use a cross product
X
to generate all pairs; - filter out the ones we don’t like (avoiding repetionts);
- turn them into sums and keeping only the sums beyond the last generated Ulam number;
- getting the minimum of those occurring only once, with a little help from Bag and other support functions.
I guess I abused of your time enough… stay safe and have -Ofun
!