{"id":19293941,"url":"https://github.com/igrishaev/zippo","last_synced_at":"2025-04-22T07:32:34.566Z","repository":{"id":58454566,"uuid":"524695934","full_name":"igrishaev/zippo","owner":"igrishaev","description":"Additions to the standard clojure.zip package","archived":false,"fork":false,"pushed_at":"2024-06-21T14:46:55.000Z","size":71,"stargazers_count":16,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-09T20:09:26.442Z","etag":null,"topics":["clojure","zippers"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/igrishaev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-08-14T14:35:22.000Z","updated_at":"2024-10-29T14:29:34.000Z","dependencies_parsed_at":"2023-01-23T07:31:34.812Z","dependency_job_id":null,"html_url":"https://github.com/igrishaev/zippo","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fzippo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fzippo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fzippo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igrishaev%2Fzippo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/igrishaev","download_url":"https://codeload.github.com/igrishaev/zippo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223893032,"owners_count":17220822,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","zippers"],"created_at":"2024-11-09T22:36:38.277Z","updated_at":"2024-11-09T22:36:38.924Z","avatar_url":"https://github.com/igrishaev.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Zippo\n\nSmall additions to the standard `clojure.zip` package.\n\n**ToC**\n\n\u003c!-- toc --\u003e\n\n- [Why](#why)\n- [Installation](#installation)\n- [Usage \u0026 examples](#usage--examples)\n  * [A finite seq of locations](#a-finite-seq-of-locations)\n  * [Finding locations](#finding-locations)\n  * [Updating a zipper](#updating-a-zipper)\n  * [Slicing a zipper by layers](#slicing-a-zipper-by-layers)\n  * [Breadth-first seq of locations](#breadth-first-seq-of-locations)\n  * [Lookups](#lookups)\n  * [A universal collection zipper](#a-universal-collection-zipper)\n  * [Also See](#also-see)\n- [ClojureScript support](#clojurescript-support)\n\n\u003c!-- tocstop --\u003e\n\n## Why\n\nThe `clojure.zip` package is a masterpiece yet misses some utility\nfunctions. For example, finding locations, bulk updates, lookups, breadth-first\ntraversing and so on. This library brings some bits of missing functionality.\n\n## Installation\n\nLein:\n\n```clojure\n[com.github.igrishaev/zippo \"0.1.4\"]\n```\n\nDeps.edn\n\n```clojure\n{com.github.igrishaev/zippo {:mvn/version \"0.1.4\"}}\n```\n\n## Usage \u0026 examples\n\nFirst, import both Zippo and `clojure.zip`:\n\n~~~clojure\n(ns zippo.core-test\n  (:require\n   [clojure.zip :as zip]\n   [zippo.core :as zippo]))\n~~~\n\nDeclare a zipper:\n\n~~~clojure\n(def z\n  (zip/vector-zip [1 [2 3] [[4]]]))\n~~~\n\nNow check out the following Zippo functions.\n\n### A finite seq of locations\n\nThe `loc-seq` funtion takes a location and returns a lazy seq of locations\nuntill it reaches the end:\n\n~~~clojure\n(let [locs (zippo/loc-seq z)]\n  (mapv zip/node locs))\n\n;; get a vector of notes to reduce the output\n[[1 [2 3] [[4]]]\n 1\n [2 3]\n 2\n 3\n [[4]]\n [4]\n 4]\n~~~\n\nThis is quite useful to traverse a zipper without keeping in mind the ending\ncondition (`zip/end?`).\n\n### Finding locations\n\nThe `loc-find` function looks for the first location that matches a predicate:\n\n~~~clojure\n(let [loc (zippo/loc-find\n           z\n           (fn [loc]\n             (-\u003e loc zip/node (= 3))))]\n\n  (is (= 3 (zip/node loc))))\n~~~\n\nAbove, we found a location which node equals 3.\n\nThe `loc-find-all` function finds all the locatins that match the predicate:\n\n~~~clojure\n(let [locs (zippo/loc-find-all\n            z\n            (zippo/-\u003eloc-pred (every-pred int? even?)))]\n\n  (is (= [2 4]\n         (mapv zip/node locs))))\n~~~\n\nSince the predicate accepts a location, you can check its children, siblings and\nso on. For example, check if a location belongs to a special kind of parent.\n\nHowever, most of the time you're interested in a value (node) rather than a\nlocation. The `-\u003eloc-pred` function converts a node predicate, which accepts a\nnode, into a location predicate. In the example above, the line\n\n~~~clojure\n(zippo/-\u003eloc-pred (every-pred int? even?))\n~~~\n\nmakes a location predicate which node is an even integer.\n\n### Updating a zipper\n\nZippo offers some functions to update a zipper.\n\nThe `loc-update` one takes a location predicate, an update function and the rest\narguments. Here is how you douple all the even numbers in a nested vector:\n\n~~~clojure\n(let [loc\n      (zippo/loc-update\n       z\n       (zippo/-\u003eloc-pred (every-pred int? even?))\n       zip/edit * 2)]\n\n  (is (= [1 [4 3] [[8]]]\n         (zip/root loc))))\n~~~\n\nFor the updating function, one may use `zip/append-child` to append a child,\n`zip/remove` to drop the entire location and so on:\n\n~~~clojure\n(let [loc\n      (zippo/loc-update\n       z\n       (fn [loc]\n         (-\u003e loc zip/node (= [2 3])))\n       zip/append-child\n       :A)]\n\n  (is (= [1 [2 3 :A] [[4]]]\n         (zip/root loc))))\n~~~\n\nThe `node-update` function is similar but acts on nodes. Instead of `loc-pred`\nand `loc-fn`, it accepts `node-pred` and `node-fn` what operate on nodes.\n\n~~~clojure\n(let [loc\n    (zippo/node-update\n     z\n     int?\n     inc)]\n(is (= [2 [3 4] [[5]]]\n       (zip/root loc))))\n~~~\n\n### Slicing a zipper by layers\n\nSometimes, you need to slice a zipper on layers. This is what is better seen on\na chart:\n\n~~~\n     +---ROOT---+    ;; layer 1\n     |          |\n   +-A-+      +-B-+  ;; layer 2\n   | | |      | | |\n   X Y Z      J H K  ;; layer 3\n~~~\n\n- Layer 1 is `[Root]`;\n- Layer 1 is `[A B]`;\n- Layer 3 is `[X Y Z J H K]`\n\nThe `loc-layers` function takes a location and builds a lazy seq of layers. The\nfirst layer is the given location, then its children, the children of children\nand so on.\n\n~~~clojure\n(let [layers\n      (zippo/loc-layers z)]\n\n  (is (= '(([1 [2 3] [[4]]])\n           (1 [2 3] [[4]])\n           (2 3 [4])\n           (4))\n         (for [layer layers]\n           (for [loc layer]\n             (zip/node loc))))))\n~~~\n\n### Breadth-first seq of locations\n\n[depth-first]: https://en.wikipedia.org/wiki/Depth-first_search\n\nThe `clojure.zip` package uses [depth-first method][depth-first] of traversing a\ntree. Let's number the items:\n\n~~~\n       +-----ROOT[1]----+\n       |                |\n +----A[2]---+     +---B[6]--+\n |     |     |     |    |    |\n X[3] Y[4] Z[5]   J[7] H[8] K[9]\n~~~\n\nThis sometimes may end up with an infinity loop when you generate children\non the fly.\n\nThe `loc-seq-breadth` functions offers the opposite way of traversing a zipper:\n\n~~~\n       +-----ROOT[1]----+\n       |                |\n +----A[2]---+     +---B[3]--+\n |     |     |     |    |    |\n X[4] Y[5] Z[6]   J[7] H[8] K[9]\n~~~\n\nThis is useful to solve some special tasks related to zippers.\n\n### Lookups\n\nWhen working with zippers, you often need such functionality as \"go\nup/left/right until meet something\". For example, from a given location, go up\nuntil a parent has a special attribute. Zippo offers four functions for that,\nnamely `lookup-up`, `lookup-left`, `lookup-right`, and `lookup-down.` All of\nthem take a location and a predicate:\n\n~~~clojure\n(let [loc\n      (zip/vector-zip [:a [:b [:c [:d]]] :e])\n\n      loc-d\n      (zippo/loc-find loc\n                      (zippo/-\u003eloc-pred\n                       (fn [node]\n                         (= node :d))))\n\n      loc-b\n      (zippo/lookup-up loc-d\n                       (zippo/-\u003eloc-pred\n                        (fn [node]\n                          (and (vector? node)\n                               (= :b (first node))))))]\n\n  (is (= :d (zip/node loc-d)))\n\n  (is (= [:b [:c [:d]]] (zip/node loc-b))))\n~~~\n\nIn the example above, first we find the `:d` location. From there, we go up\nuntil we meet `[:b [:c [:d]]]`. If there is no such a location, the result will\nbe nil.\n\n### A universal collection zipper\n\nThe `coll-zip` function builds a zipper that navigates through all the known\ncollections types, e.g. vectors, maps, map entries, lazy collections and so\non. Unlike the standard `zip/vector-zip` and `zip/seq-zip`, it works with any\ncombination of vectors and map which is quite useful in production. A brief\nexample:\n\n```clojure\n(def sample\n  [{:foo 1}\n   #{'foo 'bar 'hello}\n   (list 1 2 3 {:aa [1 2 {:haha true}]})])\n\n(-\u003e\u003e sample\n     coll-zip\n     loc-seq\n     (map zip/node))\n\n(\u003cinitial data\u003e\n {:foo 1}\n [:foo 1]\n :foo\n 1\n #{bar hello foo}\n bar\n hello\n foo\n (1 2 3 {:aa [1 2 {:haha true}]})\n 1\n 2\n 3\n {:aa [1 2 {:haha true}]}\n [:aa [1 2 {:haha true}]]\n :aa\n [1 2 {:haha true}]\n 1\n 2\n {:haha true}\n [:haha true]\n :haha\n true)\n```\n\nThe `coll-zip` zipper carries a detailed implementation of the `make-node`\nfunction. It takes into account the type of the node and properly builds a new\none from the children. It also preserves the metadata.\n\n### Also See\n\n[zippers-guide]: https://grishaev.me/en/clojure-zippers/\n\nThe code from this library was used for [Clojure Zippers manual][zippers-guide]\n-- the complete guide to zippers in Clojure from the very scratch.\n\n## ClojureScript support\n\nSince 1.3, the library supports ClojureScript as well. At least 1.9.542 version\nof ClojureScript compiler is required as the library relies on the\n[MapEntry][MapEntry] type and the [map-entry?][map-entry?] function.\n\n[MapEntry]: https://cljs.github.io/api/cljs.core/MapEntry\n[map-entry?]: https://cljs.github.io/api/cljs.core/map-entryQMARK\n\n\u0026copy; 2022 Ivan Grishaev\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figrishaev%2Fzippo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Figrishaev%2Fzippo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figrishaev%2Fzippo/lists"}