Cartwheel Letters
One of my favorite channels in the phillydev slack channel is daily_programmer
. Most days, someone posts a simple problem to that shouldn’t take too long to solve and people post their solutions in multiple languages. It is great to see how different languages look and how different people approach the same problem.
The problem
Today’s problem went something like this:
Check is a string us doing a letter cartwheel. Return true or false.
1 | isLetterCartwheel('nun') |
This problem came from the meme:
Python Solution
Usually my gut reaction is to fall back on my python to solve this. Reasoning about the problem, “what is a letter cartwheel?”, I came up with the following requirements:
- Must be more than 1 letter
- Must have an odd number of letters
- Must only have 2 characters repeated
- The two characters must be one that is the upside-down of the other
- The two characters must alternate.
So the first pass went something like this:
1 | def isLetterCartwheel(string: str) -> bool: |
So as a somewhat side note: for these kind of problems, I love doctest
. For those of you who dont know doctest
is a builtin library that allows you to write example use cases of a function being run inside of its docstring, and test
that all of the expected outputs are correctly asserted. For problems like these, it allows me to follow a TDD pattern,
where I can write a series of examples and expected outputs in the docs of the string, and write the code until the
tests pass.
The elegance of this solution in python is hinged on frozenset
. Frozensets in python are a less used data structure, but
in certain cases, are very helpful. Here is how they fit as far as python data structures:
Mutable | Immutable | |
---|---|---|
Ordered | list() |
tuple() |
Unordered/Unique | set() |
frozenset() |
So they are basically a tuple
with unique values and where order does not matter during comparison. This means that
1 | frozenset('nu') == frozenset('un') |
This means we can do comparisons in either direction quite easily. The other checks check if it is long enough, there an odd number of characters, and the last check verifies that all of the even and odd letter spaces are equal to each other.
After looking at this a bit, I managed to refactor to basically a one liner:
1 | def isLetterCartwheel(string: str) -> bool: |
The improvements:
- All conditions of a cartwheel letter are passed to an
all
- using map instead of a list comprehension allows for short-circuting
- Don’t need double letters for things like
"oo"
, because frozenset will remove the duplicate
I am pretty happy with this code snippet. It has the documentation and tests with the code, it has a very clear signal to noise ratio of what it is testing, it takes advantage of a nice python data structure, and elegantly returns a correct solution in one executable line.
Javascript Solution
I will break this into two solutions, the ES6 solution and the older pre-ES5 Javascript solution:
ES6
With ES6, we have a Set
type, however unlike python’s set
, all it enforces is uniqueness, not
unorderedness which means:
1 | new Set('ab') != new Set('ba'); |
Unfortunately, it gets worse because:
1 | new Set('ab') != new Set('ab'); |
This means we need to write our own equivalence checker:
1 | const isEqual = (a, b) => { |
This gets us our frozenset equivalent, the length checks translate over nicely, and we can use reduce
to do the odd,
even checks:
1 | var [odd, even] = Array.from(input).reduce((a,v,i) => {a[i%2].push(v); return a},[[],[]]); |
This nice little reducer takes advantage that the callback
function in javascript has a third optional input of currentIndex
,
in our case i
. We start with an initial accumulator value of [[], []]
, an odd and even array. We then add to either the
[0]
or [1]
position based on i%2
, and push the current element to that array. We then use ES6 variable unpacking
t get the odd and even letters.
Our final es6 script looks a bit like:
1 | const identity = x => x; |
Pre-ES5 / Classic Javascript.
To take out some of the magic that ES6 gives you, and doing this in old school classic javascript, it takes a little bit more:
1 | function isLetterCartwheel (string) { |
Here is a simple check that uses no magic, takes 1 pass a the string when it needs to, and does a simple compare at the end. While not as clean or elegant as the ES6 solution, it does give some insight on how to thought process is working.
Elm Solution
And lastly, for shits and giggles, I am going to try my hand at an Elm solution.
1 | import Set |
Working in Elm and being purely functional is nice and has its benefits, but makes certain other things like splitting lists by their evens and odd numbers harder.