Rail Fence Cipher

Hi everyone, it’s my first interaction with the community.
I’ve done something recently I wanna share and please get me some feedbacks :slight_smile:
Thanks

;; encript
;W . . . E . . . C . . . R . . . U . . . O . . .
;. E . R . D . S . O . E . E . R . N . T . N . E
;. . A . . . I . . . V . . . D . . . A . . . C .
; WECRUO ERDSOEERNTNE AIVDAC

;; encript with spaces
;;WRIVDN EEAEDSOEE U TOC  CRRAN

;; decript
;;W E C R U O
;;ERDSOEERNTNE
;;A I V D A C

(defn- sanitize
  [text]
  (-> text
    (clojure.string/replace #"[\s]" "_")
    (clojure.string/replace #"[^A-Za-z_]" "")))

(defn- unsanitize
  [text]
  (-> text
    (clojure.string/replace #"[^A-Za-z_]" "")
    (clojure.string/replace #"[_]" " ")))

(defn- zigzag
  [text rails]
  (let [initial-data     {:rail      0
                          :direction :up
                          :fence     (vec (repeat rails []))}
        update-direction (fn [m r d] (merge m {:direction d
                                               :rail      (cond-> r
                                                            (= d :up) inc
                                                            (= d :down) dec)}))
        fence-builder    (fn [{:keys [rail direction] :as acc} curr]
                           (let [top?    (= rail (dec rails))
                                 bottom? (= rail 0)
                                 middle? (not (or top? bottom?))]
                             (cond-> acc
                               top? (update-direction rail :down)
                               bottom? (update-direction rail :up)
                               middle? (update-direction rail direction)
                               :always (update-in [:fence rail] #(conj % curr)))))]
    (reduce fence-builder initial-data text)))

(defn- gen-hash
  [{:keys [fence]}]
  (apply str (flatten fence)))

(defn encrypt
  [text rails]
  (-> text
    sanitize
    (zigzag rails)
    gen-hash))

(is (= "WRIVDN_EEAEDSOEE_U_TOC__CRRAN"
      (encrypt "WE ARE DISCOVERED. RUN AT ONCE" 3)))

(is (= "W_VR_EEDOE_UTO_RICRDNANEASE_C"
      (encrypt "WE ARE DISCOVERED. RUN AT ONCE" 4)))

(defn- build-zigzag
  [hash rails]
  (loop [fence (:fence (zigzag hash rails))
         hash  (vec hash)
         text  []]
    (if (empty? fence)
      text
      (let [size (count (first fence))]
        (recur
          (next fence)
          (vec (drop size hash))
          (conj text (subvec hash 0 size)))))))

(defn- fill-zigzag
  [fence text rails]
  (let [initial-data     {:rail      0
                          :direction :up
                          :fence     fence
                          :text      []}
        update-direction (fn [m r d] (merge m {:direction d
                                               :rail      (cond-> r
                                                            (= d :up) inc
                                                            (= d :down) dec)}))
        fence-builder    (fn [{:keys [rail direction] :as acc} _]
                           (let [top?    (= rail (dec rails))
                                 bottom? (= rail 0)
                                 middle? (not (or top? bottom?))]
                             (cond-> acc
                               top? (update-direction rail :down)
                               bottom? (update-direction rail :up)
                               middle? (update-direction rail direction)
                               :always (as-> $
                                         (update $ :text #(conj % (first (get-in $ [:fence rail]))))
                                         (update-in $ [:fence rail] #(next %))))))]
    (reduce fence-builder initial-data (range (count text)))))

(defn gen-text
  [{:keys [text]}]
  (apply str text))

(defn decrypt
  [hash rails]
  (-> hash
    (build-zigzag rails)
    (fill-zigzag hash rails)
    gen-text
    unsanitize))

(is (= "HELLO WORLD AGAIN"
      (decrypt "HORANEL_OL_GILWDA" 3)))

(is (= "WE ARE DISCOVERED RUN AT ONCE"
      (decrypt "WRIVDN_EEAEDSOEE_U_TOC__CRRAN" 3)))

(is (= "WE ARE DISCOVERED RUN AT ONCE"
      (decrypt "W_VR_EEDOE_UTO_RICRDNANEASE_C" 4)))