Today I was practicing with the Are they the same? kata from www.codewars.com
Here’s a summary of the steps I followed to solve it.
Description
The kata goal is
Given two arrays a and b write a function comp(a, b) (compSame(a, b) in Clojure) that checks whether the two arrays have the “same” elements, with the same multiplicities. “Same” means, here, that the elements in b are the elements in a squared, regardless of the order.
The full description can be found at www.codewars.com
The initial code
The initial code has one class and a test.
1 2 3 4 5 6 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
The kata focuses on implementing the AreSame
class so it passes the tests provided by the AreSameTest
class.
It also seems a good idea to enhance the tests in AreSameTest
to further test our implementation.
Solution steps
Iteration 1 - true use case
My goal for this first iteration was to make test1
pass / green.
The comp
method will
- sort array
a
- sort array
b
- raise
a
elements to the second power - compare the elements in
a
andb
This is the resulting code:
1 2 3 4 5 6 7 8 9 10 |
|
This code has a couple of problems that will be dealt with in next iterations:
Arrays.sort()
is modifying the input parameters, which can be considered as a code smell.- The
for
loop used to compare both arrays adds a lot of code and is not expressive enough, I’m sure there are better ways to achieve the same result
Iteration 2 - false use case
The tests provided are only covering the scenario where comp
return true
. Let’s add a test for comp
returning false.
1 2 3 4 5 6 |
|
The new test is also satisfied by the current comp
implementation, so there’s no need to modify the code at this stage.
Iteration 3 - null / empty use case
The kata description mentions that we have to deal with null and empty arrays:
If a or b are nil (or null or None), the problem doesn’t make sense so return false.
If a or b are empty the result is evident by itself.
Let’s add two new tests to cover for these scenarios:
1 2 3 4 5 6 7 8 9 10 11 |
|
When running the test suite, testEmpty
is passing, but testNull
is failing: comp
needs to be modified to deal properly with null arrays an to bring the test suite back to green.
A check for null arguments is added to the comp
method to turn the tests back to pass / green.
1
|
|
and the resulting comp
method is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Iteration 4 - Different sizes use case
How will comp
deal with arrays of different sizes? Let’s add a couple of tests to check this scenarios:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Just by adding a couple of big numbers to the end of a
and b
we are making our code fail.
In order to bring the tests back to pass / green a guard to check the size of the arrays can be added:
1
|
|
and the resulting comp
method is as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Iteration 5 - Negative numbers use case
There’s a scenario that hasn’t been tested yet: what if a
contains negative numbers?
A new test is added to cover for this scenario:
1 2 3 4 5 6 |
|
When the tests are run we see this one failing miserably: the code needs to be changed to bring the test suite back to green.
The error is caused by the fact that comp
sorts a
and then raises its values to the second power, which doesn’t work well for negative numbers.
At a first stage I considered working with absolute values from the a
array. In order to apply Math.abs()
to the values in the a
array it seemed inevitable to add a new for
loop, but I felt lazy so I after googling a bit I found that lambdas
may come in to rescue.
The following code snippet returns a new array with the abs
function applied to all its elements:
1
|
|
Arrays can be also sorted by calling:
1
|
|
Here’s the resulting comp
function after making use of Arrays
, IntStreams
and lambdas
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
And that brought the tests back to pass / green. Now it’s time for some refactoring!
Iteration 5 - Refactoring
By using IStreams
the original arrays passed in to the comp
method remain untouched so we have already dealt with one of the concerns raised in our first iteration.
Let’s see how the comp
method can be refactored, and if we can get rid of the for
loop in it used for array comparison.
Now that the comp
method uses lambdas
to apply a function to all the elements of an array, why not use lambdas
to square the elements of a
? By doing so Arrays.equals
could be used to compare the resulting array with b
and save our code for all the overhead in the for
loop used for comparison.
The following code snippet applies abs
, squares and sorts the elements in the a
array:
1
|
|
Wait a minute! If we raise to the second power, abs
is redundant, so we can get rid of it:
1
|
|
And putting it all together here is the comp
method final version:
1 2 3 4 5 6 7 8 9 |
|
Here are the final versions of the classes of this kata: