I’ve been doing the Perl Weekly
Challenges. The
latest
involved colinear points and binary tree sums. (Note that this is
open until 3 January 2021.)
TASK #1 › Max Points
You are given set of co-ordinates @N.
Write a script to count maximum points on a straight line when given
co-ordinates plotted on 2-d plane.
sub mp {
my @c=@_;
my $epsilon=0.0001;
my $mxp=0;
So I do a nested search: for each (ordered) pair of coordinates A and
B, see how many of the others fall on the line they form.
foreach my $a (0..$#c-2) {
foreach my $b ($a+1..$#c-1) {
my @d=map {$c[$b][$_]-$c[$a][$_]} (0,1);
Any two points are in a straight line…
my $pil=2;
foreach my $c ($b+1..$#c) {
my $tp=0;
my @v=map {$c[$c][$_]-$c[$a][$_]} (0,1);
A bit of special-casing deals with the situation where A and B are on
the same horizontal or vertical coordinate.
if ($d[0]==0) {
if ($v[0]==0) {
$tp=1;
}
} elsif ($d[1]==0) {
if ($v[1]==0) {
$tp=1;
}
Then it's time for floating-point. Yeah, probably could have done it
all in integer. max(v[0],d[0]) % min(v[0],d[0]) == 0
and so on, but
it starts getting into special cases quite quickly. So I got lazy.
epsilon
is there because you can never trust floating-point
equality. Python has a Fraction
class, Raku has Rat
, Ruby has
Rational
and Rust has fraction
, but I basically just implemented
this same system again.
} elsif (abs($v[0]/$d[0]-$v[1]/$d[1])<$epsilon) {
$tp=1;
}
if ($tp) {
$pil++;
}
}
if ($pil > $mxp) {
$mxp=$pil;
}
}
}
return $mxp;
}
Raku is basically the same.
Python should have explicit conversion to float (though the test cases
don't check for this).
elif abs(float(v[0])/float(d[0])-float(v[1])/float(d[1])) < epsilon:
Ruby is much the same again. Rust gets more fun, not least because the
copy of the book I have uses ...
for inclusive range but the compiler
has ..=
. (And people complained about Ruby changing its syntax.)
TASK #2 › Sum Path
You are given binary tree containing numbers 0-9
only.
Write a script to sum all possible paths from root to leaf.
Rather than parse an ASCII diagram, I started with an array
representation of
the tree, with non-existing nodes represented by undef
.
is(sp(1,2,undef,3,4),13,'example 1');
is(sp(1,2,3,4,undef,5,6),26,'example 2');
Then it's back to good old FIFO buffer code. Which in Perl is trivial
(if sluggish); in other languages, a bit less so. See, the great thing
is that a Perl array isn't a first-class type: if I say
[@array,$variable] that's simply a reference to a new array consisting
of all the values of the old array
plus variable
on the end. Which
is what I want. Each entry in @path
is a simple list of all the
array indices needed to get to this point. If it has no child nodes, I
add the sum of entries to the total.
sub sp {
my @t=@_;
my $s=0;
my @path=([0]);
while (my $a=shift @path) {
my $c=($a->[-1])*2+1;
my $tn=1;
foreach my $ac ($c,$c+1) {
if ($ac <= $#t && defined $t[$ac]) {
push @path,[@{$a},$ac];
$tn=0;
}
}
if ($tn) {
$s+=sum(map {$t[$_]} @{$a});
}
}
return $s;
}
This is the sort of thing that did my head in in Raku a few months
back. And even here it's a bit dodgy; if I called my temporary
variable @a
rather than $a
it behaved differently. Also note the
double flat
plus list
; all of those are necessary in order to get
what seems to me the simple result. Raku doesn't like this use of
undef
so I substituted -1
.
sub sp (**@t) {
my $s=0;
my @path=((0));
while (@path) {
my $a=shift @path;
my $c=($a[$a.end])*2+1;
my $tn=1;
for ($c,$c+1) -> $ac {
if ($ac <= @t.end && @t[$ac] != -1) {
push @path,($a.flat,$ac).flat.list;
$tn=0;
}
}
if ($tn) {
$s+=sum(map {@t[$_]},$a.list);
}
}
return $s;
}
For Python I used the deque
type for the main buffer. It's happy
with None
though. I did end up using a temporary array variable,
which I suppose is what the other languages are doing internally.
def sp(*t):
s=0
path=collections.deque([[0]])
while len(path)>0:
a=path.popleft()
c=a[-1]*2+1
tn=1
for ac in range(c,c+2):
if ac < len(t) and t[ac] is not None:
b=a.copy()
b.append(ac)
path.append(b)
tn=0
if tn:
s += sum(t[i] for i in a)
return s
Ruby has nil
, though it doesn't need a deque
type.
def sp(*t)
s=0
path=[[0]]
while (a=path.shift) do
c=a[-1]*2+1
tn=true
c.upto(c+1) do |ac|
if ac <= t.length && !t[ac].nil? then
path.push([a,ac].flatten)
tn=false
end
end
if tn
s += a.map{|i| t[i]}.sum()
end
end
return s
end
And Rust, ah, beautiful Rust… which gets the deque
and type
conversions, as well as the explicit temporary variable.
fn sp(t: Vec<i32>) -> i32 {
let mut s: i32=0;
let mut path: VecDeque<Vec<i32>>=VecDeque::new();
path.push_back(vec![0]);
while path.len() > 0 {
let a=path.pop_front().unwrap();
let c=((a.last().unwrap())*2+1) as usize;
let mut tn=true;
for ac in c..=c+1 {
if ac < t.len() && t[ac]>-1 {
let mut b=a.clone();
b.push(ac as i32);
path.push_back(b);
tn=false;
}
}
if tn {
s+=a.iter().map(|i| t[*i as usize]).sum::<i32>();
}
}
return s;
}
But this is feeling increasingly like the right way to do things.
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.