September 10, 2018

Just Juxt #27: Transitive Closure (4clojure #84)

Transitive Closure

The transitive closure of a binary relation R on a set X is the smallest relation on X that contains R and is transitive.

For example, if X is a set of airports and x R y means "there is a direct flight from airport x to airport y" (for x and y in X), then the transitive closure of R on X is the relation R+ such that x R+ y means "it is possible to fly from x to y in one or more flights". Informally, the transitive closure gives you the set of all places you can get to from any starting place.

Write a function which generates the transitive closure of a binary relation. The relation will be represented as a set of 2 item vectors.

(ns live.test
  (:require [cljs.test :refer-macros [deftest is testing run-tests]]))
  
(defn extensions [s]
            (fn [[k v]]
              (->> s
                (filter #(= v (first %)))
                (map (juxt (constantly k) second)))))

(defn step [[s _]]
            (vector (->> s (mapcat (extensions s)) (into s))
              s))

(defn trans-closure [r]
    ((comp ffirst
          (partial drop-while (partial apply not=))
          (partial iterate step)
          (juxt identity (constantly nil))) r))

(deftest trans-closure-test
  (is (let [divides #{[8 4] [9 3] [4 2] [27 9]}]
        (= (trans-closure divides) #{[4 2] [8 4] [8 2] [9 3] [27 9] [27 3]})))
  (is (let [more-legs
            #{["cat" "man"] ["man" "snake"] ["spider" "cat"]}]
        (= (trans-closure more-legs)
           #{["cat" "man"] ["cat" "snake"] ["man" "snake"]
             ["spider" "cat"] ["spider" "man"] ["spider" "snake"]})))
  (is (let [progeny
            #{["father" "son"] ["uncle" "cousin"] ["son" "grandson"]}]
        (= (trans-closure progeny)
           #{["father" "son"] ["father" "grandson"]
             ["uncle" "cousin"] ["son" "grandson"]}))))
  
(run-tests)
Tags: coding exercises KLIPSE 4clojure Cryogen juxt