Alright, detective, one of our colleagues successfully observed our target person, Robby the robber. We followed him to a secret warehouse, where we assume to find all the stolen stuff. The door to this warehouse is secured by an electronic combination lock. Unfortunately our spy isn't sure about the PIN he saw, when Robby entered it.
He noted the PIN 1357, but he also said, it is possible that each of the digits he saw could actually be another adjacent digit (horizontally or vertically, but not diagonally). E.g. instead of the 1 it could also be the 2 or 4. And instead of the 5 it could also be the…
My colleagues Sarah and Peter and I were doing in a session of Mob programming
The goal was to solve the kata of The observed PIN, where an unreliable spy tells that he saw the PIN 1357 being used, but actually, he's not quite sure, each digit could be instead one of its neighbor on the keyboard layout. It could be 1357 but also for example 2357 or 1368.
The project was a Java project built with Maven. It contains two files: PinGuesser.java and PinGuesserTest.java. It compiles and run the unit tests in a matter of seconds, not minutes like in many Android apps. That makes for a better developer experience IMHO.
PinGuesserTest: Convert Java File to Kotlin File and manual fixes
Sarah : So I open PinGuesserTest.java and run the command. How is it called?
Peter : Convert Java File to Kotlin File
Sarah : Let's go!
Sarah : I now have a PinGuesserTest.kt . It has some errors though
Peter : Maybe apply the suggestion to optimize imports?
Sarah : Ok.
Sarah : It worked.
Me : as you see it's not perfect, but it's an awesome learning tool: you start with what you already know (in Java) and see it converted in what you want to learn (in Kotlin)
Sarah : Let me run the unit tests
Sarah : I have some weird JUnit errors
Me : Ok, so I understand that. Java has static methods while Kotlin has the concept of a companion object { ... }
Me : Its methods look like static methods but are a bit different. Here JUnit really wants static methods, and we need an annotation to make it happy
- fun testSingleDigitParameters(): Stream<Arguments> {
+ @JvmStatic fun testSingleDigitParameters(): Stream<Arguments> {
return Stream.of(
Arguments.of("1", java.util.Set.of("1", "2", "4")),
Arguments.of("2", java.util.Set.of("1", "2", "3", "5")),
@@ -61,7 +58,7 @@ internal class PinGuesserTest {
)
}
- fun invalidParams(): Stream<Arguments> {
+ @JvmStatic fun invalidParams(): Stream<Arguments> {
return Stream.of(
Arguments.of(" "),
Arguments.of("A"),
Me : It's possible to create List, Set and Map the traditional Java way, but the Kotlin standard library contains plenty of small utilities to streamline that, that would be my first change. Let me do it:
Me : that looks better. Are the unit tests still green?
Me : Something else contained in the Kotlin Standard Library are functions found in the functional programming languages like .map(), .filter(), .flatmap() and much more.
Sarah : A bit like the Java Stream API that we are using?
Me : Yes, like this but less verbose and more performant under the hood!
Peter : Can we go a step back? What does it bring us to make the code nicer like this? At the end of the day, the customer doesn't care.
Me : Well, I don't know you, but often I don't really understand the code I'm supposed to work on. I tend to work hard to simplify it and at some point it fits in my head and the solution becomes obvious.
Peter : What would it looks like here?
Me : Now that the code is in a nice functional idiomatic Kotlin, I realize that the program can be solved using a single functional construct: List.fold()
fungetPINs(observedPin:String):Set<String>{require(observedPin.all{itinmapPins}){"PIN $observedPin is invalid"}returnobservedPin.fold(initial=setOf("")){acc:Set<String>,c:Char->valpinsForChar:Set<String>=mapPins[c]!!combineSolutions(acc,pinsForChar)}}funcombineSolutions(pins1:Set<String>,pins2:Set<String>):Set<String>=pins1.flatMap{pin1->pins2.map{pin2->"$pin1$pin2"}}.toSet()