The goal of this kata is to create a calculator that can add the numbers found in a string. I found this kata on Roy Osherove’s website. Here are the instructions for doing this kata:
String Calculator Kata Overview
- Create a simple String calculator with a method int Add(string numbers)
- The method can take 0, 1, or 2 numbers, and will return their sum (for an empty string it will return 0) for example “” or “1”, or “1,2”
- Start with the simplest test case of an empty string and move to 1 and two numbers
- Remember to solve things as simply as possible so that you force yourself to write tests you did not think about
- Remember to refactor after each passing test
- Allow the Add method to handle an unknown amount of numbers
- Allow the Add method to handle new lines between numbers (instead of commas).
- The following input is OK: “1\n2,3” (will equal 6)
- The following input is NOT OK: “1,\n” (not need to price it - just clarifying)
- Support different delimiters
- To change a delimiter, the beginning of the string will contain a separate line that looks like this: “//[delimiter]\n[numbers…]” for example “//;\n1;2” should return three where the default delimiter is ‘;’.
There are more instructions to this kata but that was as far as I was able to get without going too far over the time limit. I imposed a 30 minute time limit to complete this kata.
Constraints Inspire Creative-Thinking
I want to make a very important observation here. I could have solved this kata with programming techniques with which I am familiar; however, familiarity does not breed creativity. If you want to take your creativity to the next level, you need to put constraints. Constraints are your friends. They are the ones that prod you to think about novel ways of doing things. In this kata, I added the constraint of avoiding these keywords:
It’s amazing what you’ll discover when you impose these constraints. I began to write my solution in a Lispy sort of fashion as you’ll soon see.
The Completed Kata
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
Lispifying The Solution
Since I wasn’t able to use the
case keywords, I had to think of a new way of having my program make decisions. I observed that I was using methods that returned booleans such as
include?. My mind was screaming at me: “USE AN IF STATEMENT HERE!” I refrained. All I knew was that I wanted my program to make decisions based on whether or not the results of those method calls were true. Then I had my ‘Ah ha!’ moment. Part of what led me to use the
&& operator in my code came about because I am learning Common Lisp. Here is a snippet of Lisp code that checks to see if an atom belongs to a list:
1 2 3 4 5 6
Take a look at the OR statement. If the atom
a is equal to the first item of the list in
member? returns true and it exits the function; otherwise it will evaluate the statement
member? and pass the atom
a and the remaining items in the
lst not including the first item. Did you see how you can use an a function call as a statement to be used with an
or operator? In my solution, I went ahead and using this logic except I used the
&& operator to accomplish this. Let’s walk through some of the code in the solution.
We begin by recalling the requirements of the string calculator. The first requirement is to return 0 for an empty string. To meet that requirement, I write code to query the string and ask if it is
empty?. When I pass an empty string, the response will be
true. Since the statement to the left of the
&& operator is true, the
&& must take the next step of evaluating the statement to the right.
The statement on the right is a
return statement, so we leave the
add function with the result of
0 for an empty string. The same logic applies to the next call
If the string ends with a newline character
&& operator is forced to execute the statement to the right.
In the next few lines, I took the
&& operator further. I know that all Ruby objects return
true. With that understanding, I went ahead and chained three statements with the
1 2 3
Doesn’t that cascading style remind you of Lisp? I think this is really cool! This code covers the case when the user wants to change the delimiter in the string. Instead of using commas and passing a string such as
"1,2", the user can now pass a string like this:
"//;\n1;2". Passing this kind of string to the
add method should change the delimiter from a comma to a semicolon. Let’s suppose we receive the string
"//;\n1;2". We see it starts with
"//" so the method
true. Since we are
&&ing our statements together, all of them must return true in order for all three to be executed, otherwise, we will skip to the next portion of the program. The next step involves capturing the new delimiter and caching the results to the
@delimiter instance variable. That entire statement returns true because at the end
@delimiter has a non-nil value. We move on to the next statement, which removes the delimiter information from the original string so that we can parse the number information.
This next line is my favorite one.
Since we received a cached result from the previous chain of statements for
@delimiter is a non-nil object which evaluates to
@delimiter evaluates to
true, we move to the next statement and evaluate it. I especially like how I passed the methods
+ as symbols to the
inject methods. It saved me space and communicates its intent! We split on the new delimiter, convert each string represented as a number to an integer, and then collect a running sum and return the result.
The most valuable thing I got from having done this kata were the constraints I placed on myself. They pushed me outside of the comfort of the familiar techniques I used and demanded more from me. I did notice the repetition toward the bottom of the
add method. I had some difficulty refactoring the code in time. Perhaps next time I perform this kata, I’ll have a DRYer version.