Perl Weekly Challenge 123: Ugly Numbers and Square Points

These are some answers to the Week 123 of the Perl Weekly Challenge organized by Mohammad S. Anwar.

Spoiler Alert: This weekly challenge deadline is due on August 1, 2021 at 24:00. This blog post offers some solutions to this challenge, please don’t read on if you intend to complete the challenge on your own.

Task 1: Ugly Numbers

You are given an integer $n >= 1.

Write a script to find the $nth element of Ugly Numbers.

Ugly numbers are those number whose prime factors are 2, 3 or 5. For example, the first 10 Ugly Numbers are 1, 2, 3, 4, 5, 6, 8, 9, 10, 12.

Example

Input: $n = 7
Output: 8

Input: $n = 10
Output: 12

Ugly Numbers in Raku

The is-ugly subroutine finds whether its input value is ugly by dividing it by 2, 3 and 5 as long as it can do it evenly. At the end, the number is ugly if the end result is 1.

The program then simply builds an infinite lazy list of ugly numbers. The nth ` ugly number is just the nth number of that list.

use v6;

sub is-ugly (UInt $in is copy where * > 0) {
    for 2, 3, 5 -> $div {
        $in div= $div while $in %% $div;
    }
    return $in == 1;
}
my $ugly-nrs = grep {is-ugly $_}, (1...Inf);
my $n = @*ARGS[0] // 7;
say $ugly-nrs[$n-1];

Some sample executions:

$ raku ./ugly-nrs.raku
8
-
$ raku ./ugly-nrs.raku 10
12
-
$ raku ./ugly-nrs.raku 100
1536

Ugly Numbers in Perl

The is-ugly subroutine is essentially similar to its counterpart in Raku: it finds whether its input value is ugly by dividing it by 2, 3 and 5 as long as it can do it evenly. At the end, the number is ugly if the end result is 1.

The rest or the program is quite different because there is no lazy list in Perl. So we basically use an infinite loop and test the successive integers for ugliness. The program counts the ugly numbers, and it prints out the number and exits the loop when the target range is reached.

use strict;
use warnings;
use feature "say";

sub is_ugly {
    my $in = shift;
    for my $div (2, 3, 5) {
        $in /= $div while $in % $div == 0;
    }
    return $in == 1;
}

my $n = shift // 7;
my $i = 0;
my $count = 0;
while (1) {
    $count ++;
    $i++ if is_ugly $count;
    say $count and last if $i == $n
}

Some sample executions:

$ perl ./ugly-nrs.pl
8

$ perl ./ugly-nrs.pl 10
12

$ perl ./ugly-nrs.pl 100
1536

Ugly Numbers in Scala

In Scala, we also use a while loop.

object root extends App {
  def isUgly(in: Int): Boolean = {
    var cpy = in
    val div = List(2, 3, 5);
    for (i <- div) {
      while (cpy % i == 0) {
        cpy = cpy / i
      }
    }
    if (cpy == 1) { return true }
    return false
  }
  val n = 7
  var j = 0
  var k = 0
  while (k <= n) {
    j = j + 1
    if (isUgly(j)) {
      k = k + 1
      // println(k)
      if (k == n) { println(k) }
    }
  }
}

With the hard-coded input value of 7, the program duly prints 8, the 7th ugly number.

Ugly Numbers in Python

import sys
def isUgly(n):
    for div in [2, 3, 5]:
        while (n % div == 0):
            n = n / div;
    if n == 1: 
        return True
    return False;

count = 0
i = 0
target = int(sys.argv[1])
while count <= target:
    i += 1;
    if isUgly(i):
        count += 1;
    if count == target:
        print(i)
        break

Sample output:

$ python3 ugly-nums.py 7
8

$ python3 ugly-nums.py 10
12

$ python3 ugly-nums.py 100
1536

Task 2: Square Points

You are given coordinates of four points i.e. (x1, y1), (x2, y2), (x3, y3) and (x4, y4).

Write a script to find out if the given four points form a square.

Example:

Input: x1 = 10, y1 = 20
       x2 = 20, y2 = 20
       x3 = 20, y3 = 10
       x4 = 10, y4 = 10
Output: 1 as the given coordinates form a square.

Input: x1 = 12, y1 = 24
       x2 = 16, y2 = 10
       x3 = 20, y3 = 12
       x4 = 18, y4 = 16
Output: 0 as the given coordinates doesn't form a square.

How do we determine whether four points form a square? There is undoubtedly a number of ways to do that, but it seems to me that the easiest is to check whether the four edges of the quadrilateral are equal. The problem, though, is that we can compute 6 distances between four points, 4 or which are the edges, and two the diagonals. But we don’t know in advance which distance will be the edges and which will be the diagonals. So, essentially, for the six possible distances in a square, we expect four to be equal (the edges) and 2 others with a distance equal to the edge length multiplied by the square root of 2.

This is what we find with the distances computed in the first test case provided with the task:

([x => 10 y => 20] [x => 20 y => 20]) 10
([x => 10 y => 20] [x => 20 y => 10]) 14.142135623730951
([x => 10 y => 20] [x => 10 y => 10]) 10
([x => 20 y => 20] [x => 20 y => 10]) 10
([x => 20 y => 20] [x => 10 y => 10]) 14.142135623730951
([x => 20 y => 10] [x => 10 y => 10]) 10

It seems likely that having two values for the six distances might be sufficient. But I would rather test that one of the distance values appears four times.

Square Points in Raku

People who know me know that I am not really a great fan of object-oriented programming, but, in this case, I found that implementing a very simple Point class made some sense. The dist subroutine takes two Point objects as input parameters. Otherwise, the build4point subroutine creates four points from a list of numeric parameters.

The program computes the six possible distances between the four points, and confirm that the four points form a square if there are four distances that are equal. Note that, for “oblique” squares, it might be necessary to round the distances before comparing them, but that might lead to false squares. So there is a trade-off, and I’m not sure how to handle it. The program below doesn’t try to handle such specific cases.

use v6;

class Point {
    has $.x;    # abscissa
    has $.y;    # ordinate

    method gist { return "[x => $!x y => $!y]"}
}

sub dist (Point $a, Point $b) {
    return sqrt( ($b.x - $a.x)² + ($b.y - $a.y)² );
}

sub build4points (@in) {
    my @points;
    for @in -> $x, $y {
        push @points, Point.new(x => $x, y => $y)
    }
    return @points;
}

my @tests = <10 20 20 20 20 10 10 10>, 
            <12 24 16 10 20 12 18 18>;
for @tests -> @test {
    my @p = build4points @test;
    my %dist;
    for (@p).combinations: 2 -> $c {
        %dist{dist($c[0], $c[1])}++;
    }
    # say %dist;
    print @test, " => ";
    if any(values %dist) == 4 {say 1;} else {say 0}
}

This program displays the follwing output:

$ raku .:square-points.raku
10 20 20 20 20 10 10 10 => 1
12 24 16 10 20 12 18 18 => 0

Square Points in Perl

We are not using OO-programming in Perl, but the algorithm is essentially the same.

use strict;
use warnings;
use feature qw/say/;
use Data::Dumper;

sub dist {
    my ($p1, $p2) = @_;
    sqrt(($p2->{x} - $p1->{x}) ** 2 + ($p2->{y} - $p1->{y}) ** 2);
}

sub build4points {
    my @i = @_;
    my @p;
    for (1..4) {
        push @p, { x => shift, y => shift };
    }
    return @p;
}
my @tests = ( [ qw/10 20 20 20 20 10 10 10/ ],
              [ qw/12 24 16 10 20 12 18 18/ ] );
for my $test (@tests) {
    my @points = build4points(@$test);
    my %dist;
    for my $p ( [0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3] ) {
        my $distance =  dist($points[$p->[0]], $points[$p->[1]]);
        $dist{$distance}++
    }
    # say Dumper \%dist;
    print "@$test => ";
    if ( grep { $_ == 4 } values %dist) {
        say 1;
    } else {
        say 0;
    }
}

This program displays the following output:

$ perl ./square-points.pl
10 20 20 20 20 10 10 10 => 1
12 24 16 10 20 12 18 18 => 0

## Wrapping up

The next week Perl Weekly Challenge will start soon. If you want to participate in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 23:59 BST (British summer time) on August 8, 2021. And, please, also spread the word about the Perl Weekly Challenge if you can.

Leave a comment

About laurent_r

user-pic I am the author of the "Think Perl 6" book (O'Reilly, 2017) and I blog about the Perl 5 and Raku programming languages.