I’ve been doing the Weekly
Challenges. The
latest
involved GCDs, powers of two, and an unusual numeric sequence. (Note
that this is open until 31 October 2021.)
Task 1: Two Friendly
You are given 2 positive numbers, $m
and $n
.
Write a script to find out if the given two numbers are Two
Friendly.
Two positive numbers, m and n are two friendly when gcd(m, n) = 2 ^
p where p > 0. The greatest common divisor (gcd) of a set of numbers
is the largest positive number that divides all the numbers in the
set without remainder.
Very clearly there are two parts to this: calculate the gcd, then
determine whether it's a power of 2 (and not less than 2). Therefore
each part becomes a separate function.
Some of the languages offer a built-in gcd calculator, in which case I
don't need to define that part; where that isn't the case, I use a
Euclidean algorithm.
sub gcd {
my ($m,$n)=@_;
while ($n!=0) {
($m,$n)=($n,$m % $n);
}
return $m;
}
For the power-of-two detection, a binary approach seems reasonable,
first checking that the number is large enough:
sub ispower2 {
my $n=shift;
if ($n<2) {
return 0;
}
If it is, bitwise AND the number with itself-minus-1, which will
produce a 0 result if and only if there is only one bit set. (This
should be quicker than using a general-purpose set bit counter.)
return ($n & ($n-1))==0;
}
Then the actual function to solve the task simply becomes:
sub twofriendly {
my ($m,$n)=@_;
return ispower2(gcd($m,$n))?1:0;
}
The other languages work basically the same way. Ruby and Raku have
gcd
built in; Python has it as part of math
, a core module. In
Rust I'd need to link in another crate to use the official one, so to
keep the task in one file I wrote my own. In PostScript I use the same
modular approach (yes, this is the same algorithm as the above)…
/gcd {
{
dup
3 1 roll
mod
dup 0 eq {
pop exit
} if
} loop
} bind def
/ispower2 {
dup 2 lt {
pop false
} {
dup 1 sub and 0 eq
} ifelse
} bind def
/twofriendly {
gcd ispower2
} bind def
Task 2: Fibonacci Sequence
You are given a positive number $n
.
Write a script to find out how many different sequence you can
create using Fibonacci numbers when adds up is same as given number
without repeating a number.
Well now.
There are two ways of answering this. (Probably more really.) One is
to work it through: generate a list of all Fibonacci numbers up to and
including the target, then search through combinations of them to find
the ones that add up correctly. Most simply one could do this with a
bitmask, though clearly there are lots of optimisations possible. In
Perl:
sub fib {
my $max=shift;
my @s=(1,2);
while ($s[-1] < $max) {
push @s,$s[-1]+$s[-2];
}
return \@s;
}
sub fibseq {
my $m=shift;
my @pattern=@{fib($m)};
my $o=0;
foreach my $mask (1..(1 << scalar @pattern)-1) {
my $s=0;
foreach my $bit (0..$#pattern) {
if (1<<$bit & $mask) {
$s+=$pattern[$bit];
Short-cut to avoid pointless mask calculation; more short-cuts are
certainly possible (for example, any mask value that has all the same
bits set that this one does will also exceed the target, so we needn't
look at those at all and could delete them from the list of candidate
masks, if we were keeping that as a list rather than a for-loop).
if ($s>$m) {
last;
}
}
}
if ($s==$m) {
$o++;
}
}
return $o;
}
But I didn't get into that optimisation route, because doing this for
each number 1..n
will obviously produce a sequence of integers, so
off I went to the OEIS, and there it is: A000119 Number of
representations of n as a sum of distinct Fibonacci
numbers. And that gives a selection of
formulae to generate the desired result directly (after all, we're not
being asked to generate all the sequences, just the number of them). I
choose to use a recursive formula from the late Reinhard Zumkeller,
given as:
a(n) = f(n,1,1) with f(x,y,z) = if x<y then 0^x else f(x-y,y+z,y)+f(x,y+z,y).
Because I don't want to mess with exponentiation and potential
non-integer values, I format that 0^x as a second conditional (1 if x
is 0, otherwise 0). And thus the codebase comes down to:
sub fibseq {
my $m=shift;
return f($m,1,1);
}
sub f {
my ($x,$y,$z)=@_;
if ($x < $y) {
return ($x==0)?1:0;
} else {
return f($x-$y,$y+$z,$y)+f($x,$y+$z,$y);
}
}
and works the same way across all languages, even PostScript. (Yeah, I
could do stack manipulation with e.g. index
as a deep version of
dup
rather than using a dict
for local variables, but the code
would be horrid.)
/f {
3 dict begin
/z exch def
/y exch def
/x exch def
x y lt {
x 0 eq {
1
} {
0
} ifelse
} {
x y sub y z add y f
x y z add y f
add
} ifelse
end
} bind def
Full code on
github.
Comments on this post are now closed. If you have particular grounds for adding a late comment, comment on a more recent post quoting the URL of this one.