Berlin Clock is a classic code exercise. The main challenge is to understand how time is represented, that may be confusing at first. Once you get your head around that, the algorithm itself is straightforward, so you should be able to code it fairly quickly.
This is a real clock that exists in Berlin by the away.
The Berlin Clock is composed of four rows of lights and a yellow lamp on the top (see picture above for reference).
* The top yellow light blinks every couple of seconds (on for even seconds, off for odd seconds).
* The top two rows represent hours. The lights on the top row represent five hours each, while the bottom row lamps represent one hour each.
* The bottom two rows represent minutes. Again, each third-row lamp represents 5 minutes, so there are 11 of them. Each bottom row lamp represents one minute.
For example, 4 pm (16 hours) is represented by 3 lamps on of the first row and 1 light on of the second row (3x5 + 1). Equally, 27 minutes is represented by 5 lights on the third row and 2 on the very bottom row (5x5 + 2).
You may notice that lamps on the third row are all yellow, apart from the 3rd, 6th and 9th lamps which show quarters of an hour. This is just a visual convenience as they still represent 5 minutes like the yellow lamps.
Here is the colour code for lamps:
Y = Yellow
R = Red
O = Off
Here are some examples:
00:00:00 Y OOOO OOOO OOOOOOOOOOO OOOO
13:17:01 O RROO RRRO YYROOOOOOOO YYOO
23:59:59 O RRRR RRRO YYRYYRYYRYY YYYY
24:00:00 Y RRRR RRRR OOOOOOOOOOO OOOO
Solution:
As always, start with some tests. The advice here still applies though, be sensible, don’t go crazy with test-driven development. I find it useful to test seconds, minutes and hours isolation. In a real interview, write tests one by one while you develop the code.
public class BerlinClockTest {
private BerlinClock berlinClock = new BerlinClock();
@Test
public void testBerlinClock() {
assertEquals("O RROO RRRO YYROOOOOOOO YYOO", berlinClock.berlinClock(13, 17, 1));
assertEquals("Y RRRR RRRO YYRYYROOOOO YYYY", berlinClock.berlinClock(23, 34, 42));
}
@Test
public void testSeconds() {
assertEquals("Y", berlinClock.getSeconds(0));
assertEquals("O", berlinClock.getSeconds(1));
assertEquals("Y", berlinClock.getSeconds(42));
assertEquals("O", berlinClock.getSeconds(59));
}
@Test
public void testHours() {
assertEquals("OOOO OOOO", berlinClock.getHours(0));
assertEquals("RROO RRRO", berlinClock.getHours(13));
assertEquals("RRRR RRRO", berlinClock.getHours(23));
}
@Test
public void testMinutes() {
assertEquals("YYROOOOOOOO YYOO", berlinClock.getMinutes(17));
assertEquals("YYRYYROOOOO YYYY", berlinClock.getMinutes(34));
}
}
For simplicity, we assume the Berlin Clock method accepts hours, minutes and seconds as integers. The Berlin Clock result is the concatenation of the representation of seconds, hours and minutes:
public String berlinClock(int hours, int minutes, int seconds) {
return getSeconds(seconds) + " " + getHours(hours) + " " + getMinutes(minutes);
}
The method to represent seconds is the easiest to implement. The top display blinks every second, so the method returns “Y” for even seconds and “O” odd seconds.
protected String getSeconds(int seconds) {
return seconds % 2 == 0 ? "Y" : "O";
}
To calculate the number of enabled lights on the top hour display, we divide hours
by the number of hours per light (5) The bottom display is the remainder of the previous operation:
protected String getHours(int hours) {
int numberTopHourLamps = hours / 5;
int numberBottomHourLamps = hours % 5;
StringBuilder sb = new StringBuilder();
sb.append(getLampRow(4, numberTopHourLamps, "R"))
.append(" ")
.append(getLampRow(4, numberBottomHourLamps, "R"));
return sb.toString();
}
private String getLampRow(int totalNumberLamps, int numberLampsOn, String lampSymbol) {
StringBuilder sb = new StringBuilder();
IntStream.rangeClosed(1, totalNumberLamps)
.forEach(i -> sb.append(i <= numberLampsOn ? lampSymbol : "O"));
return sb.toString();
}
The method to calculate minutes is similar to the method to calculate hours:
protected String getMinutes(int minutes) {
int numberTopMinutesLamps = minutes / 5;
int numberBottomMinutesLamps = minutes % 5;
StringBuilder sb = new StringBuilder();
IntStream.rangeClosed(1, 11)
.forEach(i -> sb.append(i <= numberTopMinutesLamps ? getMinuteLampColour(i) : "O"));
sb.append(" ");
sb.append(getLampRow(4, numberBottomMinutesLamps, "Y"));
return sb.toString();
}
Note that the lamp color of the top minute display may be yellow or red, depending on the position on the display:
private String getMinuteLampColour(int index) {
return index % 3 == 0 ? "R" : "Y";
}
Putting it all together, here is a possible Java solution for the Berlin Clock code kata:
public class BerlinClock {
public String berlinClock(int hours, int minutes, int seconds) {
return getSeconds(seconds) + " " + getHours(hours) + " " + getMinutes(minutes);
}
protected String getSeconds(int seconds) {
return seconds % 2 == 0 ? "Y" : "O";
}
protected String getHours(int hours) {
int numberTopHourLamps = hours / 5;
int numberBottomHourLamps = hours % 5;
StringBuilder sb = new StringBuilder();
sb.append(getLampRow(4, numberTopHourLamps, "R"))
.append(" ")
.append(getLampRow(4, numberBottomHourLamps, "R"));
return sb.toString();
}
protected String getMinutes(int minutes) {
int numberTopMinutesLamps = minutes / 5;
int numberBottomMinutesLamps = minutes % 5;
StringBuilder sb = new StringBuilder();
IntStream.rangeClosed(1, 11)
.forEach(i -> sb.append(i <= numberTopMinutesLamps ? getMinuteLampColour(i) : "O"));
sb.append(" ");
sb.append(getLampRow(4, numberBottomMinutesLamps, "Y"));
return sb.toString();
}
private String getLampRow(int totalNumberLamps, int numberLampsOn, String lampSymbol) {
StringBuilder sb = new StringBuilder();
IntStream.rangeClosed(1, totalNumberLamps)
.forEach(i -> sb.append(i <= numberLampsOn ? lampSymbol : "O"));
return sb.toString();
}
private String getMinuteLampColour(int index) {
return index % 3 == 0 ? "R" : "Y";
}
}
There are many other ways to resolve this problem, see other solutions here.