A programming kata is an exercise which helps a programmer hone his skills through practice and repetition.
This article is part of the series “Scala Tutorial Through Katas”. Articles are divided into easy, medium and hard. Beginners should start with easy ones and move towards more complicated once they feel more comfortable programming in Scala.
For the complete list of Scala katas and solutions please visit the index page
The article assumes that the reader is familiar with the basic usage of ScalaTest and knows how to run them from his favorite Scala IDE (ours is IntelliJ IDEA with the Scala plugin).
Tests that prove that the solution is correct are displayed below. Recommended way to solve this kata is to write the implementation for the first test, confirm that it passes and move to the next. Once all of the tests pass, the kata can be considered solved.
One possible solution is provided below the tests. Try to solve the kata by yourself first.
Berlin Clock
Create a representation of the Berlin Clock for a given time (hh::mm:ss).
The Berlin Uhr (Clock) is a rather strange way to show the time.
On the top of the clock there is a yellow lamp that blinks on/off every two seconds.
The time is calculated by adding rectangular lamps.
The top two rows of lamps are red. These indicate the hours of a day. In the top row there are 4 red lamps.
Every lamp represents 5 hours. In the lower row of red lamps every lamp represents 1 hour.
So if two lamps of the first row and three of the second row are switched on that indicates 5+5+3=13h or 1 pm.
The two rows of lamps at the bottom count the minutes. The first of these rows has 11 lamps, the second 4.
In the first row every lamp represents 5 minutes.
In this first row the 3rd, 6th and 9th lamp are red and indicate the first quarter, half and last quarter of an hour.
The other lamps are yellow. In the last row with 4 lamps every lamp represents 1 minute.
The lamps are switched on from left to right.
Y = Yellow
R = Red
O = Off
[TESTS]
class BerlinClockTest extends UnitSpec { "Yellow lamp" should "blink on/off every two seconds" in { BerlinClock.seconds(0) should be ("Y") BerlinClock.seconds(1) should be ("O") BerlinClock.seconds(59) should be ("O") } "Top hours" should "have 4 lamps" in { BerlinClock.topHours(7).length should be (4) } it should "light a red lamp for every 5 hours" in { BerlinClock.topHours(0) should be ("OOOO") BerlinClock.topHours(13) should be ("RROO") BerlinClock.topHours(23) should be ("RRRR") BerlinClock.topHours(24) should be ("RRRR") } "Bottom hours" should "have 4 lamps" in { BerlinClock.bottomHours(5).length should be (4) } it should "light a red lamp for every hour left from top hours" in { BerlinClock.bottomHours(0) should be ("OOOO") BerlinClock.bottomHours(13) should be ("RRRO") BerlinClock.bottomHours(23) should be ("RRRO") BerlinClock.bottomHours(24) should be ("RRRR") } "Top minutes" should "have 11 lamps" in { BerlinClock.topMinutes(34).length should be (11) } it should "have 3rd, 6th and 9th lamps in red to indicate first quarter, half and last quarter" in { val minutes32 = BerlinClock.topMinutes(32) minutes32(2) should be ('R') minutes32(5) should be ('R') minutes32(8) should be ('O') } it should "light a yellow lamp for every 5 minutes unless it's first quarter, half or last quarter" in { BerlinClock.topMinutes(0) should be ("OOOOOOOOOOO") BerlinClock.topMinutes(17) should be ("YYROOOOOOOO") BerlinClock.topMinutes(59) should be ("YYRYYRYYRYY") } "Bottom minutes" should "have 4 lamps" in { BerlinClock.bottomMinutes(0).length should be (4) } it should "light a yellow lamp for every minute left from top minutes" in { BerlinClock.bottomMinutes(0) should be ("OOOO") BerlinClock.bottomMinutes(17) should be ("YYOO") BerlinClock.bottomMinutes(59) should be ("YYYY") } "Berlin Clock" should "result in array with 5 elements" in { BerlinClock.convertToBerlinTime("13:17:01").size should be (5) } it should "result in correct seconds, hours and minutes" in { val berlinTime = BerlinClock.convertToBerlinTime("16:37:16") val expected = Array("Y", "RRRO", "ROOO", "YYRYYRYOOOO", "YYOO") berlinTime should equal (expected) } }
Test code can be found in the GitHub BerlinClock.scala.
[ONE POSSIBLE SOLUTION]
object BerlinClock { def convertToBerlinTime(time: String) = { val parts = time.split(":").map(_.toInt) Array( seconds(parts(2)), topHours(parts(0)), bottomHours(parts(0)), topMinutes(parts(1)), bottomMinutes(parts(1))) } def seconds(number: Int) = { if (number % 2 == 0) "Y" else "O" } def topHours(number: Int) = onOff(4, topNumberOfOnSigns(number)) def bottomHours(number: Int) = onOff(4, number % 5) def topMinutes(number: Int) = onOff(11, topNumberOfOnSigns(number), "Y").replaceAll("YYY", "YYR") def bottomMinutes(number: Int) = onOff(4, number % 5, "Y") private def onOff(lamps: Int, onSigns: Int, onSign: String = "R") = { onSign * onSigns + "O" * (lamps - onSigns) } private def topNumberOfOnSigns(number: Int) = (number - (number % 5)) / 5 }
The solution code can be found in BerlinClock.scala solution.
What was your solution? Post it as a comment so that we can compare different ways to solve this kata.
My solution (admittedly a bit over-engineered but I wanted to make it pure functional):
https://github.com/electricmonk/scala-katas/blob/master/src/main/scala/com/shaiyallin/BerlinClock.scala
tail-recursive solution to the topMinutes problem. I really like the 11 O’s and just drop to match the number of characters in acc.
def topMinutes(min: Int): String = {
def topMinutesAccumulator (min: Int, acc: String): String =
if(min topMinutesAccumulator(min – 5, acc + “R”)
case _ => topMinutesAccumulator(min – 5, acc + “Y”)
}
topMinutesAccumulator(min, “”)
}
It crushed my code for some reason. No matter:
def topMinutes(min: Int): String = {
def topMinutesAccumulator (min: Int, acc: String): String =
if(min topMinutesAccumulator(min – 5, acc + “R”)
case _ => topMinutesAccumulator(min – 5, acc + “Y”)
}
topMinutesAccumulator(min, “”)
}
Well, pull it apart with an html tool lol you see what I meant.
object BerlinClock extends App {
def convertToBerlinTime(time: String): Array[String] = {
var Array(hours,minutes,secs) = time.split(":").map(_.toInt)
Array(
this.seconds(secs),
topHour(hours),
bottomHour(hours),
topMinute(minutes),
bottomMinute(minutes)
)
}
def seconds(seconds: Int): String = if (seconds % 2 == 0) "Y" else "O"
def topHour(hours: Int): String = lamps(hours, 5, 'R', 4)
def bottomHour(hours: Int): String = lamps( hours % 5, 1, 'R', 4)
def topMinute(minutes: Int): String = lamps(minutes, 5, 'R', 11)
def bottomMinute(minutes: Int): String = lamps(minutes % 5, 1, 'Y', 4)
private def lamps(number: Int, multiples: Int, color: Char, times: Int): String = {
var result = ""
for (w = 0) color else "O")
result
}
}
maybe this time it wont screw up the for condition
for (w = 0) color else "O")