Implementation of the clock angle problem in Go


In the Explanation of an interview question related to the clock angle problem post I shared explanation and detailed solution to the clock angle problem. I provided Java implementation over. In this post, I am sharing the similar solution in Go. I am not going to present the problem and the approach to the solution as it is common to any programming language. I would recommend you read through the post as mentioned earlier for better understanding.

Implementation in Go

I created a package called clock and put the library code in clock.go file. Please note that the package name is irrelevant and you can call it whatever is best for you. That package has three methods:

  • Overlaps returns a slice of Time objects where the minute hand overlaps hour hand.
  • AngleMinutesToHours returns angle from the minute to the hour hand. It panics if the given hours or minutes value is invalid.
  • AngleHoursToMinutes returns angle from the hour to the minute hand. It panics if the given hours or minutes value is invalid.
package clock

import (
  "math"
)

const (
  minHour = 0
  maxHour = 24
  minMin  = 0
  maxMin  = 59

  fullAngle = 360

  hourAngle        = fullAngle / 12.0
  minuteAngle      = fullAngle / 60.0
  minuteDeltaAngle = minuteAngle / 60
  hourDeltaAngle   = hourAngle / 60.0
  overlapConst     = 30.0 / 5.5
)

// Time struct representing time
type Time struct {
  Hour, Min, Sec int
}

// Overlaps returns a slice of times where the minute hand overlaps hour hand
func Overlaps() []Time {
   times := make([]Time, 0, 12)

  for h := 0; h < 12; h++ {
    m, s := math.Modf(overlapConst * float64(h))
    min := int(m)
    sec := int(math.Round(s * 60))

    if sec == 60 {
      continue
    }

    times = append(times, Time{h, min, sec})
  }

  return times
}

// AngleMinutesToHours returns angle from the minute to the hour hand
func AngleMinutesToHours(h, m, s int) int {
  if h < minHour || h > maxHour || m < minMin || m > maxMin {
    panic("Invalid hours or minutes value")
  }

  h = h % 12

  ha := float64(h) * hourAngle + hourDeltaAngle * float64(m)
  ma := float64(m) * minuteAngle + minuteDeltaAngle * float64(s)

  a := int(ha - ma)

  if a < 0 {
    a = fullAngle + a
  }

  return a
}

// AngleHoursToMinutes returns angle from the hour to the minute hand
func AngleHoursToMinutes(h, m, s int) int {
  return fullAngle - AngleMinutesToHours(h, m, s)
}

You can call your package from your main program. In the following example, I call clock.AngleMinutesToHours(int, int, int) function with 1:50 time and I print out all values from the clock.Overlaps() functions.

package main

import (
  "clock"
  "fmt"
)

func main() {
  fmt.Println(clock.AngleMinutesToHours(1, 50, 0))
  for _, time := range clock.Overlaps() {
    fmt.Printf("Hour: %2d, Min: %.2d, Sec: %.2d\n", time.Hour, time.Min, time.Sec)
  }
}

We get 115 value as an angle for 1:50 and a list of hours, minutes and seconds where the minute hand overlaps the hour hand.

Hour:  0, Min: 00, Sec: 00
Hour:  1, Min: 05, Sec: 27
Hour:  2, Min: 10, Sec: 55
Hour:  3, Min: 16, Sec: 22
Hour:  4, Min: 21, Sec: 49
Hour:  5, Min: 27, Sec: 16
Hour:  6, Min: 32, Sec: 44
Hour:  7, Min: 38, Sec: 11
Hour:  8, Min: 43, Sec: 38
Hour:  9, Min: 49, Sec: 05
Hour: 10, Min: 54, Sec: 33

Testing

This post is based on an interview question. An interviewer tries to find out about your ability to develop in Go as much as possible. In many cases, you may be asked to provide a supporting testing mechanism. In Go, test functions go to files whose names end with _test.go. A test function begins with Test and accepts *testing.T argument.

I created clock_test.go file in the clock package directory.

package clock

import (
  "testing"
)

func TestOverlaps(t *testing.T) {
  times := Overlaps()

  if len(times) != 11 {
    t.Errorf("Overlaps should return 11 records, got: %d", len(times))
  }
}

func TestInvalidInput(t *testing.T) {

  f := func(f func(int, int, int) int, h, m int) {
    defer func() {
      if r := recover(); r == nil {
        t.Errorf("Function should panic for hours: %d, minutes: %d", h, m)
      }
    }()

    f(h, m, 0)
  }

  f(AngleMinutesToHours, -1, 30)
  f(AngleMinutesToHours, 25, 30)
  f(AngleMinutesToHours, 10, -1)
  f(AngleMinutesToHours, 10, 60)

  f(AngleHoursToMinutes, -1, 30)
  f(AngleHoursToMinutes, 25, 30)
  f(AngleHoursToMinutes, 10, -1)
  f(AngleHoursToMinutes, 10, 60)
}

func TestAngles(t *testing.T) {
  tests := []struct {
    h, m, r1, r2 int
  }{
    {0, 0, 0, 0},
    {12, 0, 0, 0},
    {1, 50, 115, 245},
    {13, 50, 115, 245},
    {3, 0, 90, 270},
    {15, 0, 90, 270},
    {6, 0, 180, 180},
    {18, 0, 180, 180},
    {9, 0, 270, 90},
    {21, 0, 270, 90},
  }

  for _, test := range tests {
    if r := AngleMinutesToHours(test.h, test.m, 0); r != test.r1 {
      t.Errorf("AngleMinutesToHours failed, got: %d, want: %d", r, test.r1)
    }

    if r := AngleHoursToMinutes(test.h, test.m, 0); r != test.r2 {
      t.Errorf("AngleHoursToMinutes failed, got: %d, want: %d", r, test.r2)
    }
  }
}

This class is not very complicated, and it should not be.

In order to run the test, you call go test command. You can also add -cover flag to find out code coverage. Let’s call go test -cover from our clock package directory. The output is very similar to the following one. You can see that we passed all tests and we have 100% code coverage, which is what we expected.

PASS
coverage: 100.0% of statements
ok      clock   0.006s

Couple of extra things to mention. Even though the test class is not very complicated, it has some interesting features. The TestAngles function uses anonymous struct. Usage of anonymous structs is a pretty clean and common approach for holding test data and their expected results. Another one is in TestInvalidInput function. It presents an approach of testing panic by using the recover function.

Benchmarking

Similarly to the testing class, you may be asked to measure the performance of your package. Go provides a benchmarking mechanism that measures the performance of your program on a fixed workload.

In Go, benchmark functions are very similar to the test functions. The only difference is that they are with the Benchmark prefix and they accept *testing.B parameter.

In the example below I created three benchmark functions for my key methods.

func BenchmarkOverlaps(b *testing.B) {
  // run the Fib function b.N times
  for n := 0; n < b.N; n++ {
    Overlaps()
  }
}

func BenchmarkAngleMinutesToHours(b *testing.B) {
  // run the Fib function b.N times
  for n := 0; n < b.N; n++ {
    AngleMinutesToHours(1, 50, 0)
  }
}

func BenchmarkAngleHoursToMinutes(b *testing.B) {
  // run the Fib function b.N times
  for n := 0; n < b.N; n++ {
    AngleHoursToMinutes(1, 50, 0)
  }
}

All I need to do now is to add these benchmark functions to my clock_test.go file and run go test -bench=. command from my clock package directory. The Go test command calls all your Benchmark* functions. You should get the results that look very similar to the following ones.

oos: darwin
goarch: amd64
pkg: clock
BenchmarkOverlaps-8                     10000000               198 ns/op
BenchmarkAngleMinutesToHours-8          2000000000            0.33 ns/op
BenchmarkAngleHoursToMinutes-8          200000000             9.63 ns/op
PASS
ok      clock   5.770s

Note that it is doubtful that your benchmark results will be the same. The benchmark results depend on the operating system, CPU architecture and so on. You may also run individual benchmark functions by specifying their names (without the Benchmark prefix) in the bench parameter e.g. go test -bench=Overlaps for BenchmarkOverlaps or go test -bench=Angle for BenchmarkAngleMinutesToHours and BenchmarkAngleHoursToMinutes.

Conclusion

I presented this Go solution purely for learning purposes. You should go through it and do as experiments with different values or parameters as you can. Learning different solutions by heart is never a good idea. However, learning by practice and exercise is the best approach to master any programming language.

I hope that this post will help you to improve your skills and hopefully give you a change of finding a better job or getting a better position.

Enjoy reading.


Tags:

#benchmark #clock #go #interview #test


You may also be interested in:



comments powered by Disqus