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. :)