I'm fairly new to Clojure and I struggled a bit coming up with a nice Clojure-y solution for implementing an algorithm for one of my applications. The real issue I'm trying to solve would require a large writeup in order to understand what it's about, so I've come up with a contrived problem statement that's much easier to understand which is quite similar to my original problem.
Assume you have mapped numbers to letters like so: 1->a, 2->b, 3->c, ... , 26->z
If you receive input like "4 1 25", you can easily convert this to the string "day" (because 'd' is the 4th letter in the alphabet, 'a' is the 1st, and 'y' is the 25th).
Now assume you don't have spaces as delimiters. So the input is "4125". This introduces ambiguity. "4125" can be interpreted as "4 1 25", "4 12 5", and "4 1 2 5".
Write a function that takes a number, and returns all possible strings from these letter codes.
So if you receive "4125" (the example above), the function would return: dle, day, dabe
Here is what I came up with:
; Generates map {1->a, 2->b, ... , 26->z}
(def code->letter
(into {}
(for [x (range 26)]
[(str (inc x))
(str (char (+ x (int \a))))])))
(defn possible-strings
([number]
(possible-strings (str number) 0 ""))
([number index acc]
(if (>= index (count number))
acc
(flatten (for [[k v] code->letter
:when (.startsWith number k index)]
(possible-strings number (+ index (count k)) (str acc v)))))))
; Print all the possible strings from the input "4125"
(doseq [s (possible-strings 4125)]
(println s))
So I believe this is basically a backtracking algorithm. And because it's a backtracking algorithm, it's impossible to make this tail-recursive, correct? Or maybe I'm wrong that this is a backtracking algorithm. If so, what would this kind of solution be classified as?
Anyway, the algorithm seems to work, but I'm not sure this solution is very idiomatic. The part in particular that I really don't like is having to call flatten
for each call. I can't imagine that being very efficient either for large inputs. It seems like there must be a better way to do handle that.
Like I said, I'm new to Clojure and functional programming in general, so if there are improvements elsewhere, I'd be happy to hear about them as well. A completely different approach to solving the problem is welcome as well.