Fork me on GitHub
#beginners
<
2022-02-26
>
yubrshen17:02:29

Seeking advice on whether I should exploit the symmetry in the following code:

swapped ((enigma-updated :plugboard) input-code)

        forward-passed
        (loop [index-from-right right-most-index
               translated swapped]
          (if (< index-from-right 0)
            translated
            (recur (dec index-from-right)
                   (pass-rotor (nth rotors index-from-right) :forward translated))))

        reflected ((enigma-updated :reflector) forward-passed)

        backward-passed
        (loop [index-from-left 0
               translated reflected]
          (if (< right-most-index index-from-left)
            translated
            (recur (inc index-from-left)
                   (pass-rotor (nth rotors index-from-left) :backward translated))))
I want to iterate over a sequence of rotors, once forward, and once backward, applying transformation with each rotor (pass-rotor) with the transformed outcome fed into the next iteration's rotor. Depending on the direction, the transformation is different. The two loops are symmetry, one forward, another backward, I wonder if it's a good idea to abstract them to make the code more compact. But I'm afraid that it might be harder to read. Maybe, there is already abstraction, or idiom to handle such symmetry? My current idea is that I might introduce a concept of the direction of iteration, then using the direction as parameter, to compute the operations related the direction, such as index comparison, etc. Maybe, I'm over thinking, and there might be already better solution.

ghadi18:02:41

Transformation by explicit iteration (loop/recur) is unidiomatic

ghadi18:02:15

Is the transform of each element independent? If so, use the map function

ghadi18:02:56

You said it's not, so in that case one construct is reduce

ghadi18:02:55

You can also split up into adjacent pairs (using (partition), then map over the pairs, but this won't work for your use-case because the previous element is transformed, and paired with the next element.

ghadi18:02:13

try to frame your approach in terms of simpler abstractions like sequential lists, rather than collections + indexes that happen to be sequential

ghadi18:02:57

;; ---->
(reduce (fn [transformed cur] (pass-rotor transformed cur :backward)) swapped rotors)

;; <----
(reduce (fn [transformed cur] (pass-rotor transformed cur :backward)) result-of->pass (rseq rotors))

🙏 1
👍 1
ghadi18:02:56

user=> (def coll [:a :b :c :d])
#'user/coll
user=> (seq coll)
(:a :b :c :d)
user=> (rseq coll)
(:d :c :b :a)

🙏 1
Ben Sless19:02:04

rseq is cool

ghadi19:02:01

Yeah. It's inefficient on lists/seqs, but O(1) on vectors

yubrshen01:02:01

It's so elegant! Here is the resulted improvement (the commented code is the old/bad one):

forward-passed
        (reduce (fn [transformed curr] (pass-rotor curr alphabets-length :forward transformed)) swapped (rseq rotors))
        ;; (loop [index-from-right right-most-index
        ;;        translated swapped]
        ;;   (if (< index-from-right 0)
        ;;     translated
        ;;     (recur (dec index-from-right)
        ;;            (pass-rotor (nth rotors index-from-right) alphabets-length :forward translated))))

        reflected (get
                   (get-in enigma-updated [:reflector :map])
                   forward-passed forward-passed)

        backward-passed
        (reduce (fn [transformed curr] (pass-rotor curr alphabets-length :backward transformed)) reflected rotors)
        ;; (loop [index-from-left 0
        ;;        translated reflected]
        ;;   (if (< right-most-index index-from-left)
        ;;     translated
        ;;     (recur (inc index-from-left)
        ;;            (pass-rotor (nth rotors index-from-left) alphabets-length :backward translated))))
Thanks for the teaching!

Matej Ć arlija19:02:36

Up to what point is it ok to go with just plain maps over records?

Ed19:02:29

I'm not sure that you ever have to stop using maps. There are performance advantages for protocol implementations on records, but I've never needed to swap for these reasons. What are your concerns?

✔ 1
Matej Ć arlija19:02:50

Nothing much, just wondering what general style and conventions are

Matej Ć arlija19:02:04

it's lovely to me that Clojure is so much data oriented

Ed19:02:14

Yeah, it's my preference too. I've very rarely needed records and types in my time with Clojure.

respatialized22:02:07

https://cemerick.com/blog/2011/07/05/flowchart-for-choosing-the-right-clojure-type-definition-form.html also speaks to the strengths and stability of the language that this flowchart from over a decade ago is still relevant and useful 💯

👍 2
Matej Ć arlija19:02:19

I'm struggling to find the right words but it's bonkers how "useable" Clojure is