Before start removing connections for cyclic graphs, we could try to tackle the problem with a less damaging approach.

As we all know metro lines are bidirectional, which makes sense because imagine taking the subway in the morning and having to get a taxi to come back home at the end of the day. So, the alternative here is that if a new line introduces a cycle, all the connections of that specific line could be reversed.

( defn- reverse-stations "Reversing [[A B] [B C] [C D]] to [[D C] [C B] [B C]]" [ connections ] ( map ( fn [ info ] [ ( second info ) ( first info ) ] ) ( reverse connections ) ) ) ( defn- connections-without-cycle "Check if adding the connections to a graph introduces cycle If the graph is a dag, returns the connection, otherwise returns nil" [ graph connections ] ( let [ new-graph ( apply loom.graph /digraph graph connections ) ] ( when ( loom.alg /dag? new-graph ) connections ) ) ) ( defn- valid-connection "Return the connections that has a cycle" [ graph line-config ] ( let [ line-name ( :name line-config ) connections ( partition 2 1 ( :stations line-config ) ) ] ( or ( connections-without-cycle graph connections ) ( connections-without-cycle graph ( reverse-stations connections ) ) ) ) )

;; in build-without-cycles function - (defn build-graph-without-cycles + (defn build-graph-reversal - (let [connections (partition 2 1 (:stations line)) + (let [connections (valid-connection graph line-config)

=> ( def config [ { :name "Red" :stations [ "B" "C" "D" ] } { :name "Blue" :stations [ "D" , "B" , "A" ] } ] ) => ( def g ( build-graph-reversal config ) ) => ( loom.graph /edges g ) => ( loom.graph /predecessors g "D" )

Keep in mind that the call to loom.graph/dag? represents the most expensive operation of the algorithm. It does a Topological Sorting to confirm if the graph is a DAG or not. It takes O(E+V) , where E (edges) are the stations and V (vertex) are the connections among them. Therefore, this algorithm run in O(Line*(Stations+Connections)) .

Here is the graph with a cycle