zipper

added
1.0

ns
clojure.zip

type
function

(zipper branch? children make-node root)

Creates a new zipper structure. 

branch? is a fn that, given a node, returns true if can have
children, even if it currently doesn't.

children is a fn that, given a branch node, returns a seq of its
children.

make-node is a fn that, given an existing node and a seq of
children, returns a new branch node with the supplied children.
root is the root node.

                ;; Some clojure.zip functions will overwrite clojure.core's definitions
(use 'clojure.zip)

;; You may wish to require :as in order to avoid the above
(require '[clojure.zip :as z])

;; For the purposes of keeping the examples that follow clean,
;; assume we have taken the former route: (use 'clojure.zip)

(use 'clojure.pprint)
(def p pprint)

user> (def z [[1 2 3] [4 [5 6] 7] [8 9]])
#'user/z

user> (def zp (zipper vector? seq (fn [_ c] c) z))
#'user/zp

user> zp
[[[1 2 3] [4 [5 6] 7] [8 9]] nil]

user=> (p (-> zp down))
[[1 2 3]
 {:l [],
  :pnodes [[[1 2 3] [4 [5 6] 7] [8 9]]],
  :ppath nil,
  :r ([4 [5 6] 7] [8 9])}]
 
user> (first (-> zp down))
[1 2 3]

user=> (p (-> zp down right))
[[4 [5 6] 7]
 {:l [[1 2 3]],
  :pnodes [[[1 2 3] [4 [5 6] 7] [8 9]]],
  :ppath nil,
  :r ([8 9])}]

user> (first (-> zp down right))
[4 [5 6] 7]

user=> (p (-> zp down right down right))
[[5 6]
 {:l [4],
  :pnodes [[[1 2 3] [4 [5 6] 7] [8 9]] [4 [5 6] 7]],
  :ppath
  {:l [[1 2 3]],
   :pnodes [[[1 2 3] [4 [5 6] 7] [8 9]]],
   :ppath nil,
   :r ([8 9])},
  :r (7)}]

user=> (p (-> zp down right down right down))
[5
 {:l [],
  :pnodes [[[1 2 3] [4 [5 6] 7] [8 9]] [4 [5 6] 7] [5 6]],
  :ppath
  {:l [4],
   :pnodes [[[1 2 3] [4 [5 6] 7] [8 9]] [4 [5 6] 7]],
   :ppath
   {:l [[1 2 3]],
    :pnodes [[[1 2 3] [4 [5 6] 7] [8 9]]],
    :ppath nil,
    :r ([8 9])},
   :r (7)},
  :r (6)}]

user=> (p (-> zp down right down right (replace "hello")))
["hello"
 {:changed? true,
  :l [4],
  :pnodes [[[1 2 3] [4 [5 6] 7] [8 9]] [4 [5 6] 7]],
  :ppath
  {:l [[1 2 3]],
   :pnodes [[[1 2 3] [4 [5 6] 7] [8 9]]],
   :ppath nil,
   :r ([8 9])},
  :r (7)}]

user=> (p (-> zp down right down right (replace "hello") up))
[(4 "hello" 7)
 {:changed? true,
  :l [[1 2 3]],
  :pnodes [[[1 2 3] [4 [5 6] 7] [8 9]]],
  :ppath nil,
  :r ([8 9])}]

user=> (p (-> zp down right down right (replace "hello") up root))
([1 2 3] (4 "hello" 7) [8 9])
            
                (require '[clojure.zip :as zip])

;; Adds zip support for maps.
;; (Source: http://stackoverflow.com/a/15020649/42188)
(defn map-zipper [m]
  (zip/zipper 
    (fn [x] (or (map? x) (map? (nth x 1))))
    (fn [x] (seq (if (map? x) x (nth x 1))))
    (fn [x children] 
      (if (map? x) 
        (into {} children) 
        (assoc x 1 (into {} children))))
    m))

(def m {:a 3 :b {:x true :y false} :c 4})

;; Note that hash-maps are not ordered:
(-> (map-zipper m) zip/down zip/right zip/node)
;;=> [:b {:y false, :x true}]

;; Treat nodes as [key value] pairs:
(-> (map-zipper m) 
    zip/down
    (zip/edit (fn [[k v]] [k (inc v)]))
    zip/root)
;;=> {:c 5, :b {:y false, :x true}, :a 3}
            
                ;; A version of  zipper that allows mixing maps and vectors 
;; Note that it traverses map entries too
(require '[clojure.zip :as z])
(defn map-vec-zipper [m]
  (z/zipper
    (fn [x] (or (map? x) (sequential? x)))
    seq
    (fn [p xs]
      (if (isa? (type p) clojure.lang.MapEntry)
        (into [] xs)
        (into (empty p) xs)))
    m))
(-> (map-vec-zipper [{1 [21 22] 3 [4]}])
  z/down
  (z/edit assoc :e 99)
  z/down
  ;; Note that the map does not guarantee particular entries ordering.
  z/down ;; Getting into map entry. 
  z/next
  (z/edit conj 77)
  z/root)
;;=> [{1 [21 22 77], 3 [4], :e 99}]
            
                ;; Get sequence of all visited nodes
(require '[clojure.zip :as z])
(->> (z/vector-zip [[1 2] 3 [[4 5]]])
  (iterate z/next)
  (take-while #(not (z/end? %))) ;; Zipper's "end of iteration" condition. 
  (map z/node))
;;=> ([[1 2] 3 [[4 5]]] 
;;    [1 2] 
;;    1 2
;;    3 
;;    [[4 5]] 
;;    [4 5]
;;    4 5)