More Fun with the CryptoQuip Solver in Clojure
This morning I finished up a different implementation of the CryptoQuip solver that I've been migrating from Obj-C to ruby - and now to clojure. A friend has already done a port of the logic, but he made slightly different implementation scheme, and therefore a different execution profile. This is a lot more along the lines of the the Obj-C and ruby ports, just in clojure.
What really impressed me what the exceptionally tight implementation of the functions in clojure. For the most part, the code is about the size of the ruby code, except where it comes to the reentrant attack code. There, clojure is once again, just amazing. The ruby looks like this:
# This is the recursive entry point for attempting the "Word Block" attack # on the cyphertext starting at the 'index'th word in the quip. The idea is # that we start with the provided legend, and then for each plaintext word # in the 'index'ed cypherword that matches the legend, we add those keys not # in the legend, but supplied by the plaintext to the legend, and then try # the next cypherword in the same manner. # # If this attack results in a successful decoding of the cyphertext, this # method will return true, otherwise, it will return false. def do_word_block_attack(idx, key) p = @pieces[idx] cw = p.cypherword p.possibles.each do |pt| have_solution = false; if cw.can_match?(pt, key) # good! Now let's see if we are all done with all the words if (idx == @pieces.count - 1) # make sure we can really decode the last word if key.incorporate_mapping(cw.cyphertext, pt) # if it's good, add the solution to the list if (dec = key.decode(@cyphertext)) @solutions << dec have_solution = true end end else # OK, we had a match but we have more cypherwords # to check. So, copy the legend, add in the assumed # values from the plaintext, and move to the next next_key = key.clone if next_key.incorporate_mapping(cw.cyphertext, pt) have_solution = do_word_block_attack(idx + 1, next_key) end end end # if we have any solutions - stop break if have_solution end @solutions.count > 0 end end
and the clojure code looks like:
(defn attack "Function to do the block attack on the quip, broken down into a sequence of the cyphertext words, and their lists of possible plaintext words, as well as the index in that sequence to attack, and the clue (legend) to use. This will return the first match to the attack and that's it." [quip pieces idx clue] (if (and (coll? pieces) (map? clue)) (let [{cw :cyphertext poss :possibles} (nth pieces idx) last? (= idx (dec (count pieces)))] (some identity (for [pt poss :when (matches? clue cw pt) :let [nc (merge-clue clue cw pt)] :when nc] (if last? (decode nc quip) (attack quip pieces (inc idx) nc)))))))
which is just amazingly simple and easy to follow.
The execution times don't beat those that my friend's code, but maybe it's due to loading, or maybe he'll be able to find something in the performance I missed. But you just can't beat the simplicity of the clojure code.
Just amazing!