Perl Weekly Challenge 120: Clock Angle

by Abigail

Challenge

You are given time $T in the format hh:mm.

Write a script to find the smaller angle formed by the hands of an analog clock at a given time.
HINT: A analog clock is divided up into 12 sectors. One sector represents 30 degree (360/12 = 30).

Examples

Input: $T = '03:10'
Output: 35 degree

The distance between the 2 and the 3 on the clock is 30 degree. For the 10 minutes i.e. 1/6 of an hour that have passed. The hour hand has also moved 1/6 of the distance between the 3 and the 4, which adds 5 degree (1/6 of 30). The total measure of the angle is 35 degree.

Input: $T = '04:00'
Output: 120 degree

Solution

The hint suggests we should calculate the angle the hour hand makes (compared to some base line) and the angle the minute hand makes (compared to the same base line), and calculate the difference.

We will be ignoring that.

In fact, we will not looking at any specific angle the hour or minute hands are making. Instead, we will make use of the following observations:

  1. At 00:00, the hour hand and the minute hands align, that is, the angle between them is 0°.
  2. Every minute, the angle between the hour and minute hand increases by 5.5°.

So, we can calculate the number of minutes after midnight the time indicates, and multiply that with 5.5°. This may be a number which is more than 360° — each time the minute hand catches up with the hour hand, the angle with be a multiple of 360°. So, we will take the angle module 360°.

But that may lead to a problem. The number of minutes after midnight will always be an integer, but once with multiply with 5.5, we may not have an integer left. And not every language handles a modulo operation well if not both operands are integers.

To deal with this, we will measure the angle between the hands in half-degrees. So, will multiply the number of minutes after midnight with 11, and then modulo it with 720. Since we are required to report the smaller of the angles, if the calculated angle is more than 360 half-degrees, we subtract the angle from 720.

What's left is to report the angle in degrees, which means dividing the calculated number by 2, so we're back to full degrees.

Perl

With the input time in $_:

my $MIN_PER_HOUR    =  60;
my $DIFF_PER_MINUTE =  11;
my $FULL_CIRCLE     = 720;

my ($hours, $minutes) = /[0-9]+/g;
my $angle = ($DIFF_PER_MINUTE * ($hours * $MIN_PER_HOUR + $minutes)) %
             $FULL_CIRCLE;
   $angle =  $FULL_CIRCLE - $angle if 2 * $angle >= $FULL_CIRCLE;

say $angle / 2;

Find the full program on GitHub.

AWK

BEGIN {
    FS = ":"
    DIFF_PER_MINUTE =  11
    MIN_PER_HOUR    =  60
    FULL_CIRCLE     = 720
}

We're setting FS here, so AWK will neatly split the input for us on colons, meaning we have the hours available in $1, and the minutes in $2.

{
    angle = (DIFF_PER_MINUTE * ($1 * MIN_PER_HOUR + $2)) % FULL_CIRCLE
    if (2 * angle >= FULL_CIRCLE) {
        angle = FULL_CIRCLE - angle
    }

    print (angle / 2)
}

Find the full program on GitHub.

Bash

Just like in AWK, Bash can autosplit input for us. We do this by setting IFS:

IFS=":"

DIFF_PER_MINUTE=11
MIN_PER_HOUR=60
FULL_CIRCLE=720

We can now directly read hours and minutes. But there is a catch. Hours and minutes of the form 08 or 09 will be interpreted by Bash as illegal octal numbers. That's not what we want!

We will use a trick: we prepend the number with a 1 (giving us a three digit number, starting with a 1), the subtracting 100!

while read hours minutes
do    ((hours   = "1$hours"   - 100))
      ((minutes = "1$minutes" - 100))
      ((angle = (DIFF_PER_MINUTE * (hours * MIN_PER_HOUR + minutes)) %
                                            FULL_CIRCLE))
      if ((2 * angle >= FULL_CIRCLE))
      then ((angle = FULL_CIRCLE - angle))
      fi

Now, Bash only has integer division. So, we first divide angle by 2 and print the result. Then we check whether angle is an odd number; if it is, we print .5, as the result should have an additional half degree.

      printf "%d" $((angle / 2))
      if ((angle % 2 == 1))
      then printf ".5"
      fi
      echo
done

Find the full program on GitHub.

bc

The calculations in our bc solution is very similar as the solutions in other languages, but we need to do something special when printing the result:

diff_per_minute =  11
min_per_hour    =  60
full_circle     = 720

hours   = read ()
minutes = read ()

scale = 0
angle = (diff_per_minute * (hours * min_per_hour + minutes)) % full_circle
if (2 * angle >= full_circle) {
    angle = full_circle - angle
}

scale = angle % 2

if (angle == 1) {
    "0"
}

angle / 2

By default, assuming the -l isn't used, bc uses integer arithmetic. We can change this by assigning to the special variable scale, which tells bc to how many decimals after the comma you want results.

But setting it to 1 at the beginning of the program and leaving it at 1 causes two issues: first, it will always print the number of degrees with one decimal after the decimal dot — even if the result is an integer. Furthermore, the modulus operator doesn't work as it does when scale is set to 0.

So, we first set the scale to 0, then, before printing the result set scale to 1 iff angle is odd. The result is that if the hands make an angle which is an integer amount of degrees, we print an integer; otherwise, the result will be an integer and a half.

Another oddity is that bc prints a half as .5, not 0.5. To counteract that, if angle equals 1 (so, one half-degree), we first print a 0 (which then will be followed by .5, giving the end result of 0.5).

Also note that in bc, all variables are in lowercase.

Find the full program on GitHub.

C

Nothing special about the C solution. C uses integer division if both its operands are integers, so a similar check as in our Bash solution on whether angle is odd, and, if so, print .5:

# define DIFF_PER_MINUTE  11
# define MIN_PER_HOUR     60
# define FULL_CIRCLE     720

int main (void) {
    int hours, minutes;

    while (scanf ("%d:%d", &hours, &minutes) == 2) {
        int angle = (DIFF_PER_MINUTE * (hours * MIN_PER_HOUR + minutes)) %
                                                FULL_CIRCLE;
        if (2 * angle >= FULL_CIRCLE) {
            angle = FULL_CIRCLE - angle;
        }
        printf ("%d", angle / 2);
        if (angle % 2) {
            printf (".5");
        }
        printf ("\n");
    }
    return (0);
}

Find the full program on GitHub.

Go

In Go, things are similar:

import (
    "fmt"
)

var DIFF_PER_MINUTE =  11;
var MIN_PER_HOUR    =  60;
var FULL_CIRCLE     = 720;

func main () {
    var hours, minutes int;
    for {
        var n, err = fmt . Scanf ("%d:%d", &hours, &minutes)
        if (err != nil || n != 2) {
            break;
        }
        var angle = (DIFF_PER_MINUTE * (hours * MIN_PER_HOUR + minutes)) %
                                                FULL_CIRCLE;
        if (2 * angle >= FULL_CIRCLE) {
            angle = FULL_CIRCLE - angle;
        }

        fmt . Print (angle / 2);
        if (angle % 2 == 1) {
            fmt . Print (".5")
        }
        fmt . Print ("\n")
    }
}

Find the full program on GitHub.

Java

Java is a bit more verbose, but no surprises here:

import java.util.*;


public class ch2 {
    public static void main (String [] args) {
        int DIFF_PER_MINUTE =  11;  // Half degrees
        int MIN_PER_HOUR    =  60;
        int FULL_CIRCLE     = 720;  // Half degrees
        Scanner scanner = new Scanner (System . in);
        try {
            while (true) {
                String line = scanner . nextLine ();
                String [] parts = line . split (":");
                int hours   = Integer . parseInt (parts [0]);
                int minutes = Integer . parseInt (parts [1]);

                int angle = (DIFF_PER_MINUTE *
                            (hours * MIN_PER_HOUR + minutes)) % FULL_CIRCLE;

                if (2 * angle >= FULL_CIRCLE) {
                    angle = FULL_CIRCLE - angle;
                }

                System . out . print (angle / 2);
                if (angle % 2 == 1) {
                    System . out . print (".5");
                }
                System . out . print ("\n");
            }
        }
        catch (Exception e) {
            //
            // EOF
            //
        }
    }
}

Find the full program on GitHub.

Lua

local DIFF_PER_MINUTE =  11
local MIN_PER_HOUR    =  60
local FULL_CIRCLE     = 720

for line in io . lines () do
    local _, _, hours, minutes = line : find ("([0-9][0-9]):([0-9][0-9])")
    hours   = tonumber (hours)
    minutes = tonumber (minutes)
    local angle = (DIFF_PER_MINUTE * (hours * MIN_PER_HOUR + minutes)) %
                                              FULL_CIRCLE
    if 2 * angle >= FULL_CIRCLE
    then angle = FULL_CIRCLE - angle
    end

    print (angle / 2)
end

Find the full program on GitHub.

Node.js

let DIFF_PER_MINUTE =  11
let MIN_PER_HOUR    =  60
let FULL_CIRCLE     = 720

  require ('readline')
. createInterface ({input: process . stdin})   
. on              ('line', line => {
    let [hours, minutes] = line . trim () . split (":")
    angle = (DIFF_PER_MINUTE * (+hours * MIN_PER_HOUR + +minutes)) %
                                         FULL_CIRCLE
    if (2 * angle >= FULL_CIRCLE) {
        angle = FULL_CIRCLE - angle
    }

    console . log (angle / 2)
})

Find the full program on GitHub.

Free Pascal

ses sysutils;

var
    time: string;
    hours, minutes, angle: integer;

const
    DIFF_PER_MINUTE =  11;
    MIN_PER_HOUR    =  60;
    FULL_CIRCLE     = 720;

begin
    while not eof () do begin
        readln (time);
        hours   := strtoint (leftstr  (time, 2));
        minutes := strtoint (rightstr (time, 2));

        angle := (DIFF_PER_MINUTE * (hours * MIN_PER_HOUR + minutes)) mod
                                             FULL_CIRCLE;

        if 2 * angle >= FULL_CIRCLE then begin
            angle := FULL_CIRCLE - angle;
        end;

        write (angle div 2);
        if angle mod 2 = 1 then begin
            write ('.5');
        end;
        writeln ('');
    end
end.

Find the full program on GitHub.

Python

Not much special about the Python solution:

import fileinput

DIFF_PER_MINUTE =  11
MIN_PER_HOUR    =  60
FULL_CIRCLE     = 720

for line in fileinput . input ():
    hours, minutes = line . strip () . split (":")
    angle = (DIFF_PER_MINUTE * (int (hours) * MIN_PER_HOUR + int (minutes))) \
                                            % FULL_CIRCLE
    if 2 * angle >= FULL_CIRCLE:
        angle = FULL_CIRCLE - angle

    print (int (angle / 2), end = '')
    if angle % 2:
        print (".5", end = '')
    print ("")

Find the full program on GitHub.

R

R has a slightly usual assignment operator (<-), which came to R from APL. Indexing arrays requires double brackets. Other than that, the R solution is very similar to the solutions in other languages:

DIFF_PER_MINUTE <-  11
MIN_PER_HOUR    <-  60
FULL_CIRCLE     <- 720

stdin   <- file ('stdin', 'r')
time    <- readLines (stdin, n = 1)
parts   <- strsplit (time, ":")
hours   <- as.numeric (parts [[1]] [[1]])
minutes <- as.numeric (parts [[1]] [[2]])
angle   <- (DIFF_PER_MINUTE * (hours * MIN_PER_HOUR + minutes)) %%
                                       FULL_CIRCLE
if (2 * angle >= FULL_CIRCLE) {
    angle <- FULL_CIRCLE - angle
}
cat (angle / 2, "\n")

Find the full program on GitHub.

Ruby

diff_per_minute =  11
min_per_hour    =  60
full_circle     = 720

ARGF . each_line do
    |time|
    hours, minutes = time . split (/:/)
    angle = (diff_per_minute * (hours . to_i * min_per_hour + minutes . to_i))\
                                             % full_circle
    angle = full_circle - angle if 2 * angle >= full_circle

    print (angle / 2)
    print (".5") if angle % 2 == 1
    print ("\n")
end

Find the full program on GitHub.

Tcl

Tcl suffers from the same issue as Bash: if a value starts with a 0, tcl treats it as an octal number, which causes a problem for 08 and 09. So, we use the same technique we used in Bash: prepend a 1 to the number, then subtract 100:

set DIFF_PER_MINUTE   11
set MIN_PER_HOUR      60
set FULL_CIRCLE      720

while {[gets stdin line] >= 0} {
    set parts [split $line :]
    set hours   [expr 1[lindex $parts 0] - 100]
    set minutes [expr 1[lindex $parts 1] - 100]
    set angle   [expr (($DIFF_PER_MINUTE * \
                       ($hours * $MIN_PER_HOUR + $minutes))) % $FULL_CIRCLE]
    if {2 * $angle >= $FULL_CIRCLE} {
        set angle [expr $FULL_CIRCLE - $angle]
    }

    puts -nonewline [expr $angle / 2]
    if {$angle % 2 == 1} {
        puts -nonewline ".5"
    }
    puts ""
}

Find the full program on GitHub.

Befunge-93

Without comments:

> & :1+!#@_ ~$ 543*** &+ 65+ * 65432**** % :   6543*** `#v_v
^                                                        v v
^   v<<<<<<<<<<<<<<<<<<<<<<<<<<  /2 :  < -\ ****23456 :  < v
^   v                                  ^<<<<<<<<<<<<<<<<<<<<
^   v
^   >>> : 56+9* `!#v_ : 554** / "0"+, 554** % > : 55+ / "0"+, > 55+% "0"+, v 
^                  v                          ^               ^            v
^                  >>>>>>>>>>>>>>>>>>>  : 9 `#^_  >>>>>>>>>>>>^            v
^                                                                          v
^                                                    v  ,,".5"  <          v
^                                                    v          ^          v
^<<<<<<<<<<<<<<<<<<<<<<<<<<  , +55  <<<<<<<<<<<<<<<<<<<<<<<<<  _^#  !%2  <<<

Find the full program on GitHub.


Please leave any comments as a GitHub issue.