Topological sort

338 views
Skip to first unread message

Nick Day

unread,
Nov 11, 2009, 2:04:03 PM11/11/09
to Clojure
Hi,

I've been trying to implement a topological sort and have been
struggling a bit. I have a map of symbol vs collection of symbols
like:

{a [b c], b [c], c [nil]}

which can be read as 'a' depends on 'b' and 'c', 'b' depends on 'c'
and 'c' doesn't depend on anything. I've been trying to write
something that returns a collection of the dependencies in order (c,
b, a) but so far I've only been able to do it using a ref to store
intermediate output of the sorting. I was wondering if anyone has
already done something similar?

cheers,
Nick

jan

unread,
Nov 12, 2009, 1:01:39 AM11/12/09
to clo...@googlegroups.com
clojure.contrib.graph/dependency-list might be close enough for you

(use 'clojure.contrib.graph)

(dependency-list {:nodes [:a :b :c]
:neighbors {:a [:b :c]
:b [:c]}})
=>[#{:c} #{:b} #{:a}]

--
jan

John Harrop

unread,
Nov 12, 2009, 1:06:26 AM11/12/09
to clo...@googlegroups.com
You mean pure functionally?

First, I'd represent no dependencies as an empty vector rather than a vector with a single nil in it.

Then, consider how you get the first few entries: you find all the nodes with no dependencies.

How do you get the next few? The nodes whose only dependencies are nodes you already have.

This suggests the algorithm:

(defn find-a-node [deps already-have-nodes]
  (some (fn [[k v]] (if (empty? (remove already-have-nodes v)) k)) deps))

This will return a key from deps whose corresponding value contains no objects not in the set already-have-nodes.

Next, we just need to apply it repeatedly until it comes up empty:

(defn order-nodes [deps]
  (loop [deps deps already-have-nodes #{} output []]
    (if (empty? deps)
      output
      (if-let [item (find-a-node deps already-have-nodes)]
        (recur
          (dissoc deps item)
          (conj already-have-nodes item)
          (conj output item))
        (throw (Exception. "Circular dependency."))))))

This seems to work:

user=> (order-nodes {1 [2 3] 2 [3] 3 []})
[3 2 1]
user=> (order-nodes {1 [2 3] 2 [3] 3 [2]})
#<CompilerException java.lang.Exception: Circular dependency. (NO_SOURCE_FILE:0)>

No refs, atoms, or other mutable state, or cheating using Java mutable objects or set-var-root!, etc.; rewriting the above with iterate, reduce, or similar instead of loop/recur is left as an exercise for the reader. :)

Nick Day

unread,
Nov 16, 2009, 8:38:44 AM11/16/09
to Clojure
brilliant, this has been very helpful - thanks to both for taking the
time to answer!



On Nov 12, 6:06 am, John Harrop <jharrop...@gmail.com> wrote:

Christophe Grand

unread,
Nov 16, 2009, 10:43:46 AM11/16/09
to clo...@googlegroups.com
Just for the fun of writing it:

(defn topological-sort [x]
(mapcat
#(for [[k v] % :when (empty? v)] k)
(take-while seq
(iterate #(into {} (for [[k v] % :when (seq v)] [k (mapcat % v)])) x))))

user=> (topological-sort {1 [2 3] 2 [3] 3 []})
(3 2 1)

Note that it's not the same algorithm as Jon's and it doesn't detect cycles.

Christophe
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your
> first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en



--
Professional: http://cgrand.net/ (fr)
On Clojure: http://clj-me.cgrand.net/ (en)

Matching Socks

unread,
Jun 15, 2018, 10:22:28 AM6/15/18
to Clojure

user> (topological-sort {0 [1] 1 [2 3] 2 [3] 3 []})
(3 2 0 1)

!
Reply all
Reply to author
Forward
0 new messages