RabbitFarm
2021-08-01
Ugly Numbers / Square Points
The examples used here are from The Weekly Challenge problem statement and demonstrate the working solution.
Part 1
You are given an integer $n >= 1. Write a script to find the $nth Ugly Number.
Solution
use strict;
use warnings;
use boolean;
sub prime_factor{
my $x = shift(@_);
my @factors;
for (my $y = 2; $y <= $x; $y++){
next if $x % $y;
$x /= $y;
push @factors, $y;
redo;
}
return @factors;
}
sub is_ugly{
my($x) = @_;
for my $factor (prime_factor($x)){
return false if $factor != 2 && $factor != 3 && $factor !=5;
}
return true;
}
sub nth_ugly{
my($n) = @_;
return 1 if $n == 1;
my $ugly_count = 1;
my $i = 1;
do{
$i++;
$ugly_count++ if is_ugly($i);
}while($ugly_count != $n);
return $i;
}
MAIN:{
my($N);
$N = 7;
print nth_ugly($N) . "\n";
$N = 10;
print nth_ugly($N) . "\n";
}
Sample Run
$ perl perl/ch-1.pl
8
12
Notes
I also worked this problem in Prolog and C++ and, unsurprisingly, the Perl code is the shortest. All three solutions followed the same approach but Perl's syntax is naturally less verbose without making comprehension of the code more difficult.
Part 2
You are given co-ordinates for four points. Write a script to find out if the given four points form a square.
Solution
use strict;
use warnings;
use boolean;
use Math::GSL::Vector;
sub unique{
my %seen;
return grep {!$seen{$_}++} @_;
}
sub is_square{
my @points = @_;
##
# Definitely a square if there are only 2 x and 2 y values.
##
my @x = unique(map {$_->[0]} @points);
my @y = unique(map {$_->[1]} @points);
return true if @x == 2 && @y == 2;
##
# sort the points and compute side lengths
##
my @sorted_x = sort {$a->[0] <=> $b->[0]} @points;
my @sorted_y = sort {$a->[1] <=> $b->[1]} @points;
my($s, $t, $u, $v) = ($sorted_y[@sorted_y - 1], $sorted_x[@sorted_x - 1], $sorted_y[0], $sorted_x[0]);
return false if $s->[0] + $u->[0] != $t->[0] + $v->[0];
return false if $s->[1] + $u->[1] != $t->[1] + $v->[1];
return false if $s->[1] - $u->[1] != $t->[0] - $v->[0];
##
# compute angles
##
my $dv_st = new Math::GSL::Vector([$s->[0] - $t->[0], $s->[1] - $t->[1]]);
my $dv_tu = new Math::GSL::Vector([$t->[0] - $u->[0], $t->[1] - $u->[1]]);
my $dv_uv = new Math::GSL::Vector([$u->[0] - $v->[0], $u->[1] - $v->[1]]);
my $dv_vs = new Math::GSL::Vector([$v->[0] - $s->[0], $v->[1] - $s->[1]]);
return false if $dv_st * $dv_tu != 0;
return false if $dv_tu * $dv_uv != 0;
return false if $dv_uv * $dv_vs != 0;
return true;
}
MAIN:{
my @points;
@points = ([10, 20], [20, 20], [20, 10], [10, 10]);
print is_square(@points) . "\n";
@points = ([12, 24], [16, 10], [20, 12], [18, 16]);
print is_square(@points) . "\n";
@points = ([-3, 1], [4, 2], [9, -3], [2, -4]);
print is_square(@points) . "\n";
@points = ([0, 0], [2, 1], [3, -1], [1, -2]);
print is_square(@points) . "\n";
}
Sample Run
$ perl perl/ch-2.pl
1
0
0
1
Notes
The logic of determining if the points determine a square is clear to most people familiar with geometry:
- Are there only two each of X and Y co-ordinates? Then that is enough to establish that we have a square.
- Otherwise, make sure the side lengths are all equivalent and that the angles between the sides are all 90 degrees.
The code in is_square()
works through that logic with multiple exit points set up along
the way. Perhaps this is a bit odd looking but I have been doing a lot of logic
programming in Prolog recently and thought to give a somewhat more logical style to this
perl solution to this problem. Developing a more logical style for Perl is a bit of a work
in progress for me, I will admit!
The unique
function (and it's clever use of grep
!) was taken from a
PerlMaven article.
References
posted at: 17:00 by: Adam Russell | path: /perl | permanent link to this entry
The Weekly Challenge 123 (Prolog Solutions)
The examples used here are from the weekly challenge problem statement and demonstrate the working solution.
Part 1
You are given an integer n >= 1. Write a script to find the nth Ugly Number.
Solution
:-initialization(main).
prime_factors(N, L):-
N > 0,
prime_factors(N, L, 2).
prime_factors(1, [], _):-
!.
prime_factors(N, [F|L], F):-
R is N // F,
N =:= R * F,
!,
prime_factors(R, L, F).
prime_factors(N, L, F):-
next_factor(N, F, NF),
prime_factors(N, L, NF).
next_factor(_, 2, 3):-
!.
next_factor(N, F, NF):-
F * F < N,
!,
NF is F + 2.
next_factor(N, _, N).
ugly(N, UglyNumber):-
ugly(N, 1, 1, _, UglyNumber).
ugly(1, _, _, _, 1).
ugly(N, _, N, UglyNumber, UglyNumber).
ugly(N, X, I, _, UglyNumber):-
prime_factors(X, Factors),
member(Factor, Factors),
(Factor == 2; Factor == 3; Factor == 5),
X0 is X + 1,
I0 is I + 1,
ugly(N, X0, I0, X, UglyNumber).
ugly(N, X, I, UglyNext, UglyNumber):-
X0 is X + 1,
ugly(N, X0, I, UglyNext, UglyNumber).
main:-
ugly(10, UglyNumber),
write(UglyNumber), nl,
halt.
Sample Run
$ gplc prolog/ch-1.p
$ prolog/ch-1
12
Notes
Here the first N ugly numbers are generated in a pretty routine way. Much of the code is related to computing the prime factors. Once that is out of way the rest of the code seems to be straightforward to follow: recursively counting up each Ugly Number until we reach the Nth one.
Part 2
You are given coordinates of four points. Write a script to find out if the given four points form a square.
Solution
:-initialization(main).
dot_product(X0-Y0, X1-Y1, N):-
N0 is X0 * X1,
N is N0 + Y0 * Y1.
swap_key_value([], []).
swap_key_value([A-B|R], [B-A|S]):-
swap_key_value(R, S).
square(Points):-
setof(X, member(X-_, Points), Xs),
setof(Y, member(_-Y, Points), Ys),
length(Xs, LXs),
length(Ys, LYs),
keysort(Points, PointsByX),
swap_key_value(Points, Swapped),
keysort(Swapped, PointsByY0),
swap_key_value(PointsByY0, PointsByY),
last(PointsByY, Sx-Sy),
last(PointsByX, Tx-Ty),
nth(1, PointsByY, Ux-Uy),
nth(1, PointsByX, Vx-Vy),
SUx is Sx + Ux,
TVx is Tx + Vx,
SUy is Sy + Uy,
TVy is Ty + Vy,
SUym is Sy - Uy,
TVxm is Tx - Vx,
DVSTx is Sx - Tx,
DVSTy is Sy - Ty,
DVTUx is Tx - Ux,
DVTUy is Ty - Uy,
DVUVx is Ux - Vx,
DVUVy is Uy - Vy,
DVVSx is Vx - Sx,
DVVSy is Vy - Sy,
dot_product(DVSTx-DVSTy, DVTUx-DVTUy, DPSTU),
dot_product(DVTUx-DVTUy, DVUVx-DVUVy, DPTUV),
dot_product(DVUVx-DVUVy, DVVSx-DVVSy, DPUVS),
((LXs == 2, LYs == 2); (SUx == TVx, SUy == TVy, SUym == TVxm, DPSTU == 0, DPTUV == 0, DPUVS == 0)).
main:-
((square([10-20, 20-20, 20-10, 10-10]), write(1)); (write(0))),
nl,
((square([12-24, 16-10, 20-12, 18-16]), write(1)); (write(0))),
nl,
((square([-3-1, 4-2, -(9,-3), -(2,-4)]), write(1)); (write(0))),
nl,
((square([0-0, 2-1, -(3,-1), -(1,-2)]), write(1)); (write(0))),
nl,
halt.
Sample Run
$ gplc prolog/ch-2.p
$ prolog/ch-2
1
0
0
1
Notes
This is most likely the most tedious Prolog code I have written in a long time! The actual logic of determining if the points determine a square is not so bad:
- Are there only two each of X and Y co-ordinates? Then that is enough to establish that we have a square.
- Otherwise, make sure the side lengths are all equivalent and that the angles between the sides are all 90 degrees.
The tedious part is just all the computation of the distance vectors, the sorting and arranging of the points, and so forth.
The points are represented as pairs. To
orient the points they are sorted by X (key) or Y (value). This is both done by the
builtin keysort/2
predicate, with keys and values swapped to facilitate the sorting
by value.
In the case of negative co-ordinates we use the alternative (-)/2
syntax. For example,
-(3,-1)
is used since 3--1
is not valid syntactically.
In the example output we see that the first and last sets of points are both squares. The first is an example of a square with two unique X and Y co-ordinates. The third example is a rhombus and so is a good test to make sure the angles are being checked correctly.
References
posted at: 16:58 by: Adam Russell | path: /prolog | permanent link to this entry