Skip to content

Instantly share code, notes, and snippets.

@jkrasnay
Created September 28, 2017 18:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jkrasnay/7b749cdc19525ecabb089e7f73476715 to your computer and use it in GitHub Desktop.
Save jkrasnay/7b749cdc19525ecabb089e7f73476715 to your computer and use it in GitHub Desktop.
Clojure Topo Sort
(ns topo-sort.core)
;; Graph Topological Sort
;;
;; The topo-sort function here performs a toplogical sort of the graph. A
;; topological sort returns a list of nodes in dependency order. In our case, a
;; graph is represented as a collection of directed edges, each of which is a
;; two element vector with the "from node" and the "to node":
;;
;; (topo-sort #{[:a :b] [:b :c] [:a :c]})
;; => [:a :b :c]
;;
;; The nodes need not be keywords. They can be anything that can be compared
;; for equality.
;;
;; We use Kuhn's algorithm (source: https://en.wikipedia.org/wiki/Topological_sorting)
;;
;; L ← Empty list that will contain the sorted elements
;; S ← Set of all nodes with no incoming edge
;; while S is non-empty do
;; remove a node n from S
;; add n to tail of L
;; for each node m with an edge e from n to m do
;; remove edge e from the graph
;; if m has no other incoming edges then
;; insert m into S
;; if graph has edges then
;; return error (graph has at least one cycle)
;; else
;; return L (a topologically sorted order)
(defn incoming
"Given a collection of edges, return all those that are incoming edges to node n."
[edges n]
(filter (fn [[x y]] (= y n)) edges))
(defn outgoing
"Given a collection of edges, return all those that are outgoing edges from node n."
[edges n]
(filter (fn [[x y]] (= x n)) edges))
(defn remove-edges
"Returns a vector of [edges s], where edges is the input edges collection
with all outgoing edges from n removed, and s is a collection of nodes to add
to our start nodes."
[edges n]
(loop [edges edges
og (outgoing edges n)
s []]
(if (seq og)
(let [edge (first og)
[_ m] edge
new-edges (remove #(= % edge) edges)] ; TODO make edges a set, use disj
(if (= 0 (count (incoming new-edges m)))
(recur new-edges (rest og) (conj s m))
(recur new-edges (rest og) s)))
[edges s])))
(defn start-nodes
"Returns all nodes that have no incoming edges."
[edges]
(remove (set (map second edges)) (set (map first edges)))) ; TODO make edges a set, use disj
(defn topo-sort
"Performs a topological sort of the graph implied by `edges`. `edges` is a
collection of edges of the form `[x y]`, where x is the from-node and y is
the to-node of the edge."
[edges]
(loop [edges edges
s (start-nodes edges)
L []]
(if (seq s)
(let [n (first s)
[edges' s'] (remove-edges edges n)]
(recur edges' (into (rest s) s') (conj L n)))
(if (seq edges)
[] ; FAIL, if we have edges left we don't have a DAG!
L))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment