├── .gitignore ├── README.md ├── epl-v10.html ├── project.clj ├── src └── weavejester │ └── dependency.cljc └── test └── weavejester └── dependency_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | .lein* 3 | /pom.xml 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dependency 2 | 3 | A data structure for representing dependency graphs in Clojure. 4 | 5 | This library provides a basic implementation of a directed acyclic 6 | graph (DAG) data structure, represented as a pair of maps. It is 7 | immutable and persistent. 8 | 9 | Nodes in the graph may be any type which supports Clojure's equality 10 | semantics such as keywords, symbols, or strings. 11 | 12 | I originally developed this library to support namespace dependency 13 | tracking in [tools.namespace], where it is still included under the 14 | name `clojure.tools.namespace.dependency`. 15 | 16 | I am releasing this library independently so that other projects can 17 | use it without adding a dependency on all of tools.namespace. 18 | 19 | [tools.namespace]: https://github.com/clojure/tools.namespace 20 | 21 | 22 | ## Releases and Dependency Information 23 | 24 | This library is released on [Clojars]. Latest release is 0.2.1 25 | 26 | [Leiningen] dependency information: 27 | 28 | [weavejester/dependency "0.2.1"] 29 | 30 | [Clojars]: http://clojars.org/ 31 | [Leiningen]: http://leiningen.org/ 32 | 33 | 34 | ## Version Compatibility 35 | 36 | Version 0.2.0 requires Clojure 1.7.0 or higher for 37 | Conditional Read (`.cljc`) support. 38 | 39 | Version 0.2.0 also supports ClojureScript versions with 40 | Conditional Read. 41 | 42 | Version 0.1.1 of this library remains compatible with 43 | Clojure versions 1.3.0, 1.4.0, and 1.5.1. 44 | 45 | 46 | ## Usage 47 | 48 | (require '[weavejester.dependency :as dep]) 49 | 50 | Create a new dependency graph: 51 | 52 | (def g1 (-> (dep/graph) 53 | (dep/depend :b :a) ; "B depends on A" 54 | (dep/depend :c :b) ; "C depends on B" 55 | (dep/depend :c :a) ; "C depends on A" 56 | (dep/depend :d :c))) ; "D depends on C" 57 | 58 | This creates a structure like the following: 59 | 60 | :a 61 | / | 62 | :b | 63 | \ | 64 | :c 65 | | 66 | :d 67 | 68 | Ask questions of the graph: 69 | 70 | (dep/transitive-dependencies g1 :d) 71 | ;;=> #{:a :c :b} 72 | 73 | (dep/depends? g1 :d :b) 74 | ;;=> true 75 | 76 | Get a topological sort: 77 | 78 | (dep/topo-sort g1) 79 | ;;=> (:a :b :c :d) 80 | 81 | Refer to the docstrings for more API documentation. Refer to the tests 82 | for more examples. 83 | 84 | 85 | ## Development and Contributing 86 | 87 | This library is a repackaging of `clojure.tools.namespace.dependency`. 88 | All changes must go through [tools.namespace] and the Clojure 89 | [contributing] process. 90 | 91 | 92 | ## Change Log 93 | 94 | * Release 0.2.1 on 21-Jan-2018 95 | * Add secondary comparator to `topo-sort` for deterministic ordering 96 | * Release 0.2.0 on 18-Sept-2015 97 | * Convert to `.cljc` for ClojureScript support 98 | * Apply enhancements and fixes up to [tools.namespace] 99 | version 0.3.0-alpha1 100 | * Release 0.1.1 on 03-Jun-2013 101 | * A node may not depend on itself; fixes [#1] 102 | * Release 0.1.0 on 07-Apr-2013 103 | 104 | [#1]: https://github.com/stuartsierra/dependency/pull/1 105 | [tools.namespace]: https://github.com/clojure/tools.namespace 106 | [contributing]: http://dev.clojure.org/display/community/Contributing 107 | 108 | 109 | ## Copyright and License 110 | 111 | Copyright (c) Stuart Sierra, 2012-2015. All rights reserved. The use 112 | and distribution terms for this software are covered by the Eclipse 113 | Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 114 | which can be found in the file epl-v10.html at the root of this 115 | distribution. By using this software in any fashion, you are agreeing 116 | to be bound by the terms of this license. You must not remove this 117 | notice, or any other, from this software. 118 | -------------------------------------------------------------------------------- /epl-v10.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 |Eclipse Public License - v 1.0
31 | 32 |THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.
36 | 37 |1. DEFINITIONS
38 | 39 |"Contribution" means:
40 | 41 |a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and
43 |b) in the case of each subsequent Contributor:
44 |i) changes to the Program, and
45 |ii) additions to the Program;
46 |where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.
54 | 55 |"Contributor" means any person or entity that distributes 56 | the Program.
57 | 58 |"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.
61 | 62 |"Program" means the Contributions distributed in accordance 63 | with this Agreement.
64 | 65 |"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.
67 | 68 |2. GRANT OF RIGHTS
69 | 70 |a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.
76 | 77 |b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.
88 | 89 |c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.
101 | 102 |d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.
105 | 106 |3. REQUIREMENTS
107 | 108 |A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:
110 | 111 |a) it complies with the terms and conditions of this 112 | Agreement; and
113 | 114 |b) its license agreement:
115 | 116 |i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;
120 | 121 |ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;
124 | 125 |iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and
128 | 129 |iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.
133 | 134 |When the Program is made available in source code form:
135 | 136 |a) it must be made available under this Agreement; and
137 | 138 |b) a copy of this Agreement must be included with each 139 | copy of the Program.
140 | 141 |Contributors may not remove or alter any copyright notices contained 142 | within the Program.
143 | 144 |Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.
147 | 148 |4. COMMERCIAL DISTRIBUTION
149 | 150 |Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.
172 | 173 |For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.
183 | 184 |5. NO WARRANTY
185 | 186 |EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.
197 | 198 |6. DISCLAIMER OF LIABILITY
199 | 200 |EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
208 | 209 |7. GENERAL
210 | 211 |If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.
216 | 217 |If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.
223 | 224 |All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.
232 | 233 |Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.
252 | 253 |This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.
258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject weavejester/dependency "0.2.1" 2 | :description "A data structure for representing dependency graphs" 3 | :url "https://github.com/weavejester/dependency" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :profiles {:dev {:dependencies [[org.clojure/clojure "1.9.0"]]} 7 | :clj-1.8.0 {:dependencies [[org.clojure/clojure "1.8.0"]]} 8 | :clj-1.7.0 {:dependencies [[org.clojure/clojure "1.7.0"]]}}) 9 | -------------------------------------------------------------------------------- /src/weavejester/dependency.cljc: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Stuart Sierra, 2012-2015. All rights reserved. The use and 2 | ;; distribution terms for this software are covered by the Eclipse Public 3 | ;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can be 4 | ;; found in the file epl-v10.html at the root of this distribution. By using 5 | ;; this software in any fashion, you are agreeing to be bound by the terms of 6 | ;; this license. You must not remove this notice, or any other, from this 7 | ;; software. 8 | 9 | (ns ^{:author "Stuart Sierra" 10 | :doc "Bidirectional graphs of dependencies and dependent objects."} 11 | weavejester.dependency 12 | (:require [clojure.set :as set])) 13 | 14 | (defprotocol DependencyGraph 15 | (immediate-dependencies [graph node] 16 | "Returns the set of immediate dependencies of node.") 17 | (immediate-dependents [graph node] 18 | "Returns the set of immediate dependents of node.") 19 | (transitive-dependencies [graph node] 20 | "Returns the set of all things which node depends on, directly or 21 | transitively.") 22 | (transitive-dependencies-set [graph node-set] 23 | "Returns the set of all things which any node in node-set depends 24 | on, directly or transitively.") 25 | (transitive-dependents [graph node] 26 | "Returns the set of all things which depend upon node, directly or 27 | transitively.") 28 | (transitive-dependents-set [graph node-set] 29 | "Returns the set of all things which depend upon any node in 30 | node-set, directly or transitively.") 31 | (nodes [graph] 32 | "Returns the set of all nodes in graph.")) 33 | 34 | (defprotocol DependencyGraphUpdate 35 | (depend [graph node dep] 36 | "Returns a new graph with a dependency from node to dep (\"node depends 37 | on dep\"). Forbids circular dependencies.") 38 | (remove-edge [graph node dep] 39 | "Returns a new graph with the dependency from node to dep removed.") 40 | (remove-all [graph node] 41 | "Returns a new dependency graph with all references to node removed.") 42 | (remove-node [graph node] 43 | "Removes the node from the dependency graph without removing it as a 44 | dependency of other nodes. That is, removes all outgoing edges from 45 | node.")) 46 | 47 | (defn- remove-from-map [amap x] 48 | (reduce (fn [m [k vs]] 49 | (assoc m k (disj vs x))) 50 | {} (dissoc amap x))) 51 | 52 | (defn- transitive 53 | "Recursively expands the set of dependency relationships starting 54 | at (get neighbors x), for each x in node-set" 55 | [neighbors node-set] 56 | (loop [unexpanded (mapcat neighbors node-set) 57 | expanded #{}] 58 | (if-let [[node & more] (seq unexpanded)] 59 | (if (contains? expanded node) 60 | (recur more expanded) 61 | (recur (concat more (neighbors node)) 62 | (conj expanded node))) 63 | expanded))) 64 | 65 | (declare depends?) 66 | 67 | (def set-conj (fnil conj #{})) 68 | 69 | (defrecord MapDependencyGraph [dependencies dependents] 70 | DependencyGraph 71 | (immediate-dependencies [graph node] 72 | (get dependencies node #{})) 73 | (immediate-dependents [graph node] 74 | (get dependents node #{})) 75 | (transitive-dependencies [graph node] 76 | (transitive dependencies #{node})) 77 | (transitive-dependencies-set [graph node-set] 78 | (transitive dependencies node-set)) 79 | (transitive-dependents [graph node] 80 | (transitive dependents #{node})) 81 | (transitive-dependents-set [graph node-set] 82 | (transitive dependents node-set)) 83 | (nodes [graph] 84 | (clojure.set/union (set (keys dependencies)) 85 | (set (keys dependents)))) 86 | DependencyGraphUpdate 87 | (depend [graph node dep] 88 | (when (or (= node dep) (depends? graph dep node)) 89 | (throw (ex-info (str "Circular dependency between " 90 | (pr-str node) " and " (pr-str dep)) 91 | {:reason ::circular-dependency 92 | :node node 93 | :dependency dep}))) 94 | (MapDependencyGraph. 95 | (update-in dependencies [node] set-conj dep) 96 | (update-in dependents [dep] set-conj node))) 97 | (remove-edge [graph node dep] 98 | (MapDependencyGraph. 99 | (update-in dependencies [node] disj dep) 100 | (update-in dependents [dep] disj node))) 101 | (remove-all [graph node] 102 | (MapDependencyGraph. 103 | (remove-from-map dependencies node) 104 | (remove-from-map dependents node))) 105 | (remove-node [graph node] 106 | (MapDependencyGraph. 107 | (dissoc dependencies node) 108 | dependents))) 109 | 110 | (defn graph "Returns a new, empty, dependency graph." [] 111 | (->MapDependencyGraph {} {})) 112 | 113 | (defn depends? 114 | "True if x is directly or transitively dependent on y." 115 | [graph x y] 116 | (contains? (transitive-dependencies graph x) y)) 117 | 118 | (defn dependent? 119 | "True if y is a dependent of x." 120 | [graph x y] 121 | (contains? (transitive-dependents graph x) y)) 122 | 123 | (defn topo-sort 124 | "Returns a topologically-sorted list of nodes in graph. Takes an 125 | optional comparator to provide secondary sorting when the order of 126 | nodes is ambiguous." 127 | ([graph] 128 | (topo-sort (constantly 0) graph)) 129 | ([comp graph] 130 | (loop [sorted () 131 | g graph 132 | todo (set (filter #(empty? (immediate-dependents graph %)) 133 | (nodes graph)))] 134 | (if (empty? todo) 135 | sorted 136 | (let [[node & more] (sort #(comp %2 %1) todo) 137 | deps (immediate-dependencies g node) 138 | [add g'] (loop [deps deps 139 | g g 140 | add #{}] 141 | (if (seq deps) 142 | (let [d (first deps) 143 | g' (remove-edge g node d)] 144 | (if (empty? (immediate-dependents g' d)) 145 | (recur (rest deps) g' (conj add d)) 146 | (recur (rest deps) g' add))) 147 | [add g]))] 148 | (recur (cons node sorted) 149 | (remove-node g' node) 150 | (clojure.set/union (set more) (set add)))))))) 151 | 152 | (def ^:private max-number 153 | #?(:clj Long/MAX_VALUE 154 | :cljs js/Number.MAX_VALUE)) 155 | 156 | (defn topo-comparator 157 | "Returns a comparator fn which produces a topological sort based on 158 | the dependencies in graph. Nodes not present in the graph will sort 159 | after nodes in the graph. Takes an optional secondary comparator to 160 | provide secondary sorting when the order of nodes is ambiguous." 161 | ([graph] 162 | (topo-comparator (constantly 0) graph)) 163 | ([comp graph] 164 | (let [pos (zipmap (topo-sort comp graph) (range))] 165 | (fn [a b] 166 | (let [pos-a (get pos a) 167 | pos-b (get pos b)] 168 | (if (and (nil? pos-a) (nil? pos-b)) 169 | (comp a b) 170 | (compare (or pos-a max-number) 171 | (or pos-b max-number)))))))) 172 | -------------------------------------------------------------------------------- /test/weavejester/dependency_test.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Stuart Sierra, 2012-2015. All rights reserved. The use and 2 | ;; distribution terms for this software are covered by the Eclipse Public 3 | ;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can be 4 | ;; found in the file epl-v10.html at the root of this distribution. By using 5 | ;; this software in any fashion, you are agreeing to be bound by the terms of 6 | ;; this license. You must not remove this notice, or any other, from this 7 | ;; software. 8 | 9 | (ns weavejester.dependency-test 10 | (:require [clojure.test :refer [deftest is are]] 11 | [weavejester.dependency :refer :all])) 12 | 13 | ;; building a graph like: 14 | ;; 15 | ;; :a 16 | ;; / | 17 | ;; :b | 18 | ;; \ | 19 | ;; :c 20 | ;; | 21 | ;; :d 22 | ;; 23 | (def g1 (-> (graph) 24 | (depend :b :a) ; "B depends on A" 25 | (depend :c :b) ; "C depends on B" 26 | (depend :c :a) ; "C depends on A" 27 | (depend :d :c))) ; "D depends on C" 28 | 29 | ;; 'one 'five 30 | ;; | | 31 | ;; 'two | 32 | ;; / \ | 33 | ;; / \ | 34 | ;; / \ / 35 | ;; 'three 'four 36 | ;; | / 37 | ;; 'six / 38 | ;; | / 39 | ;; | / 40 | ;; | / 41 | ;; 'seven 42 | ;; 43 | (def g2 (-> (graph) 44 | (depend 'two 'one) 45 | (depend 'three 'two) 46 | (depend 'four 'two) 47 | (depend 'four 'five) 48 | (depend 'six 'three) 49 | (depend 'seven 'six) 50 | (depend 'seven 'four))) 51 | 52 | ;; :level0 53 | ;; / | | \ 54 | ;; ----- | | ----- 55 | ;; / | | \ 56 | ;; :level1a :level1b :level1c :level1d 57 | ;; \ | | / 58 | ;; ----- | | ----- 59 | ;; \ | | / 60 | ;; :level2 61 | ;; / | | \ 62 | ;; ----- | | ----- 63 | ;; / | | \ 64 | ;; :level3a :level3b :level3c :level3d 65 | ;; \ | | / 66 | ;; ----- | | ----- 67 | ;; \ | | / 68 | ;; :level4 69 | ;; 70 | ;; ... and so on in a repeating pattern like that, up to :level26 71 | 72 | (def g3 (-> (graph) 73 | (depend :level1a :level0) 74 | (depend :level1b :level0) 75 | (depend :level1c :level0) 76 | (depend :level1d :level0) 77 | (depend :level2 :level1a) 78 | (depend :level2 :level1b) 79 | (depend :level2 :level1c) 80 | (depend :level2 :level1d) 81 | 82 | (depend :level3a :level2) 83 | (depend :level3b :level2) 84 | (depend :level3c :level2) 85 | (depend :level3d :level2) 86 | (depend :level4 :level3a) 87 | (depend :level4 :level3b) 88 | (depend :level4 :level3c) 89 | (depend :level4 :level3d) 90 | 91 | (depend :level5a :level4) 92 | (depend :level5b :level4) 93 | (depend :level5c :level4) 94 | (depend :level5d :level4) 95 | (depend :level6 :level5a) 96 | (depend :level6 :level5b) 97 | (depend :level6 :level5c) 98 | (depend :level6 :level5d) 99 | 100 | (depend :level7a :level6) 101 | (depend :level7b :level6) 102 | (depend :level7c :level6) 103 | (depend :level7d :level6) 104 | (depend :level8 :level7a) 105 | (depend :level8 :level7b) 106 | (depend :level8 :level7c) 107 | (depend :level8 :level7d) 108 | 109 | (depend :level9a :level8) 110 | (depend :level9b :level8) 111 | (depend :level9c :level8) 112 | (depend :level9d :level8) 113 | (depend :level10 :level9a) 114 | (depend :level10 :level9b) 115 | (depend :level10 :level9c) 116 | (depend :level10 :level9d) 117 | 118 | (depend :level11a :level10) 119 | (depend :level11b :level10) 120 | (depend :level11c :level10) 121 | (depend :level11d :level10) 122 | (depend :level12 :level11a) 123 | (depend :level12 :level11b) 124 | (depend :level12 :level11c) 125 | (depend :level12 :level11d) 126 | 127 | (depend :level13a :level12) 128 | (depend :level13b :level12) 129 | (depend :level13c :level12) 130 | (depend :level13d :level12) 131 | (depend :level14 :level13a) 132 | (depend :level14 :level13b) 133 | (depend :level14 :level13c) 134 | (depend :level14 :level13d) 135 | 136 | (depend :level15a :level14) 137 | (depend :level15b :level14) 138 | (depend :level15c :level14) 139 | (depend :level15d :level14) 140 | (depend :level16 :level15a) 141 | (depend :level16 :level15b) 142 | (depend :level16 :level15c) 143 | (depend :level16 :level15d) 144 | 145 | (depend :level17a :level16) 146 | (depend :level17b :level16) 147 | (depend :level17c :level16) 148 | (depend :level17d :level16) 149 | (depend :level18 :level17a) 150 | (depend :level18 :level17b) 151 | (depend :level18 :level17c) 152 | (depend :level18 :level17d) 153 | 154 | (depend :level19a :level18) 155 | (depend :level19b :level18) 156 | (depend :level19c :level18) 157 | (depend :level19d :level18) 158 | (depend :level20 :level19a) 159 | (depend :level20 :level19b) 160 | (depend :level20 :level19c) 161 | (depend :level20 :level19d) 162 | 163 | (depend :level21a :level20) 164 | (depend :level21b :level20) 165 | (depend :level21c :level20) 166 | (depend :level21d :level20) 167 | (depend :level22 :level21a) 168 | (depend :level22 :level21b) 169 | (depend :level22 :level21c) 170 | (depend :level22 :level21d) 171 | 172 | (depend :level23a :level22) 173 | (depend :level23b :level22) 174 | (depend :level23c :level22) 175 | (depend :level23d :level22) 176 | (depend :level24 :level23a) 177 | (depend :level24 :level23b) 178 | (depend :level24 :level23c) 179 | (depend :level24 :level23d) 180 | 181 | (depend :level25a :level24) 182 | (depend :level25b :level24) 183 | (depend :level25c :level24) 184 | (depend :level25d :level24) 185 | (depend :level26 :level25a) 186 | (depend :level26 :level25b) 187 | (depend :level26 :level25c) 188 | (depend :level26 :level25d))) 189 | 190 | (deftest t-transitive-dependencies 191 | (is (= #{:a :c :b} 192 | (transitive-dependencies g1 :d))) 193 | (is (= '#{two four six one five three} 194 | (transitive-dependencies g2 'seven)))) 195 | 196 | (deftest t-transitive-dependencies-deep 197 | (is (= #{:level0 198 | :level1a :level1b :level1c :level1d 199 | :level2 200 | :level3a :level3b :level3c :level3d 201 | :level4 202 | :level5a :level5b :level5c :level5d 203 | :level6 204 | :level7a :level7b :level7c :level7d 205 | :level8 206 | :level9a :level9b :level9c :level9d 207 | :level10 208 | :level11a :level11b :level11c :level11d 209 | :level12 210 | :level13a :level13b :level13c :level13d 211 | :level14 212 | :level15a :level15b :level15c :level15d 213 | :level16 214 | :level17a :level17b :level17c :level17d 215 | :level18 216 | :level19a :level19b :level19c :level19d 217 | :level20 218 | :level21a :level21b :level21c :level21d 219 | :level22 220 | :level23a :level23b :level23c :level23d} 221 | (transitive-dependencies g3 :level24))) 222 | (is (= #{:level0 223 | :level1a :level1b :level1c :level1d 224 | :level2 225 | :level3a :level3b :level3c :level3d 226 | :level4 227 | :level5a :level5b :level5c :level5d 228 | :level6 229 | :level7a :level7b :level7c :level7d 230 | :level8 231 | :level9a :level9b :level9c :level9d 232 | :level10 233 | :level11a :level11b :level11c :level11d 234 | :level12 235 | :level13a :level13b :level13c :level13d 236 | :level14 237 | :level15a :level15b :level15c :level15d 238 | :level16 239 | :level17a :level17b :level17c :level17d 240 | :level18 241 | :level19a :level19b :level19c :level19d 242 | :level20 243 | :level21a :level21b :level21c :level21d 244 | :level22 245 | :level23a :level23b :level23c :level23d 246 | :level24 247 | :level25a :level25b :level25c :level25d} 248 | (transitive-dependencies g3 :level26)))) 249 | 250 | 251 | (deftest t-transitive-dependents 252 | (is (= '#{four seven} 253 | (transitive-dependents g2 'five))) 254 | (is (= '#{four seven six three} 255 | (transitive-dependents g2 'two)))) 256 | 257 | (defn- before? 258 | "True if x comes before y in an ordered collection." 259 | [coll x y] 260 | (loop [[item & more] (seq coll)] 261 | (cond (nil? item) true ; end of the seq 262 | (= x item) true ; x comes first 263 | (= y item) false 264 | :else (recur more)))) 265 | 266 | (deftest t-before 267 | (is (true? (before? [:a :b :c :d] :a :b))) 268 | (is (true? (before? [:a :b :c :d] :b :c))) 269 | (is (false? (before? [:a :b :c :d] :d :c))) 270 | (is (false? (before? [:a :b :c :d] :c :a)))) 271 | 272 | (deftest t-topo-comparator-1 273 | (let [sorted (sort (topo-comparator g1) [:d :a :b :foo])] 274 | (are [x y] (before? sorted x y) 275 | :a :b 276 | :a :d 277 | :a :foo 278 | :b :d 279 | :b :foo 280 | :d :foo))) 281 | 282 | (deftest t-topo-comparator-2 283 | (let [sorted (sort (topo-comparator g2) '[three seven nine eight five])] 284 | (are [x y] (before? sorted x y) 285 | 'three 'seven 286 | 'three 'eight 287 | 'three 'nine 288 | 'five 'eight 289 | 'five 'nine 290 | 'seven 'eight 291 | 'seven 'nine))) 292 | 293 | (deftest t-topo-comparator-3 294 | (is (= (sort (topo-comparator compare g2) '[three seven nine eight five]) 295 | '[five three seven eight nine]))) 296 | 297 | (deftest t-topo-sort-1 298 | (let [sorted (topo-sort g2)] 299 | (are [x y] (before? sorted x y) 300 | 'one 'two 301 | 'one 'three 302 | 'one 'four 303 | 'one 'six 304 | 'one 'seven 305 | 'two 'three 306 | 'two 'four 307 | 'two 'six 308 | 'two 'seven 309 | 'three 'six 310 | 'three 'seven 311 | 'four 'seven 312 | 'five 'four 313 | 'five 'seven 314 | 'six 'seven))) 315 | 316 | (deftest t-topo-sort-2 317 | (is (= (topo-sort compare g1) '[:a :b :c :d])) 318 | (is (= (topo-sort compare g2) '[five one two four three six seven])) 319 | (is (= (topo-sort compare g3) 320 | '[:level0 321 | :level1a :level1b :level1c :level1d 322 | :level2 323 | :level3a :level3b :level3c :level3d 324 | :level4 325 | :level5a :level5b :level5c :level5d 326 | :level6 327 | :level7a :level7b :level7c :level7d 328 | :level8 329 | :level9a :level9b :level9c :level9d 330 | :level10 331 | :level11a :level11b :level11c :level11d 332 | :level12 333 | :level13a :level13b :level13c :level13d 334 | :level14 335 | :level15a :level15b :level15c :level15d 336 | :level16 337 | :level17a :level17b :level17c :level17d 338 | :level18 339 | :level19a :level19b :level19c :level19d 340 | :level20 341 | :level21a :level21b :level21c :level21d 342 | :level22 343 | :level23a :level23b :level23c :level23d 344 | :level24 345 | :level25a :level25b :level25c :level25d 346 | :level26]))) 347 | 348 | (deftest t-no-cycles 349 | (is (thrown? Exception 350 | (-> (graph) 351 | (depend :a :b) 352 | (depend :b :c) 353 | (depend :c :a))))) 354 | 355 | (deftest t-no-self-cycles 356 | (is (thrown? Exception 357 | (-> (graph) 358 | (depend :a :b) 359 | (depend :a :a))))) 360 | --------------------------------------------------------------------------------