|
| 1 | +(ns lab6.indexed |
| 2 | + (:require [clojure.edn :as edn] |
| 3 | + [clojure.string :as str])) |
| 4 | + |
| 5 | +(defn variable? [x] (and (symbol? x) (= \? (first (name x))))) |
| 6 | + |
| 7 | +(def ? '?) |
| 8 | +(def x 'x) |
| 9 | + |
| 10 | +(defn normalize [_ pattern] (mapv #(if (variable? %) ? x) pattern)) |
| 11 | + |
| 12 | +(defmulti get-from-index normalize) |
| 13 | +(defmethod get-from-index [x x x] [{idx :spo} [s p o]] (let [os (get-in idx [s p])] (if (get os o) [[]] []))) |
| 14 | +(defmethod get-from-index [x x ?] [{idx :spo} [s p o]] (map vector (keys (get-in idx [s p])))) |
| 15 | +(defmethod get-from-index [x ? x] [{idx :osp} [s p o]] (map vector (keys (get-in idx [o s])))) |
| 16 | +(defmethod get-from-index [x ? ?] [{idx :spo} [s p o]] (let [edx (idx s)] (for [[p om] edx o (keys om)] [p o]))) |
| 17 | +(defmethod get-from-index [? x x] [{idx :pos} [s p o]] (map vector (keys (get-in idx [p o])))) |
| 18 | +(defmethod get-from-index [? x ?] [{idx :pos} [s p o]] (let [edx (idx p)] (for [[o sm] edx s (keys sm)] [s o]))) |
| 19 | +(defmethod get-from-index [? ? x] [{idx :osp} [s p o]] (let [edx (idx o)] (for [[s pm] edx p (keys pm)] [s p]))) |
| 20 | +(defmethod get-from-index [? ? ?] [{idx :spo} [s p o]] (for [[s pom] idx [p om] pom o (keys om)] [s p o])) |
| 21 | + |
| 22 | +(defn add-to-index |
| 23 | + [index [_ _ c :as triple]] |
| 24 | + (assoc-in index triple c)) |
| 25 | + |
| 26 | +(defprotocol Index |
| 27 | + (add [this triple]) |
| 28 | + (match [this pattern])) |
| 29 | + |
| 30 | +(defrecord IndexedGraph [spo pos osp] |
| 31 | + Index |
| 32 | + (add [this [s p o :as triple]] |
| 33 | + (IndexedGraph. (add-to-index spo triple) |
| 34 | + (add-to-index pos [p o s]) |
| 35 | + (add-to-index osp [o s p]))) |
| 36 | + (match [this pattern] |
| 37 | + {:vars (vec (filter variable? pattern)) |
| 38 | + :bindings (get-from-index this pattern)})) |
| 39 | + |
| 40 | +(defn load-data |
| 41 | + [filename] |
| 42 | + (let [data (edn/read-string (slurp filename))] |
| 43 | + (reduce add (IndexedGraph. {} {} {}) data))) |
| 44 | + |
| 45 | +(defn export-data |
| 46 | + [filename data] |
| 47 | + (let [triples (:bindings (match data '[?s ?p ?o]))] |
| 48 | + (spit filename (pr-str (vec triples))))) |
| 49 | + |
| 50 | +(defn do-filter |
| 51 | + [match-result filter-expr] |
| 52 | + (let [vars (:vars match-result) |
| 53 | + data (:bindings match-result) |
| 54 | + fltr (first filter-expr) |
| 55 | + filter-fn (eval `(fn [~vars] ~fltr))] |
| 56 | + {:vars vars |
| 57 | + :bindings (filter filter-fn data)})) |
| 58 | + |
| 59 | +(defn rewrite-pattern |
| 60 | + [vars binding-data pattern] |
| 61 | + (let [binding-map (zipmap vars binding-data)] |
| 62 | + (mapv #(if (variable? %) (get binding-map % %) %) pattern))) |
| 63 | + |
| 64 | +(defn join |
| 65 | + [graph part-result pattern] |
| 66 | + (let [vars (:vars part-result) |
| 67 | + new-vars (->> pattern |
| 68 | + (filter variable?) |
| 69 | + (remove (set vars))) |
| 70 | + result (for [binding (:bindings part-result) |
| 71 | + new-values (:bindings (match graph (rewrite-pattern vars binding pattern)))] |
| 72 | + (vec (concat binding new-values)))] |
| 73 | + {:vars (vec (concat vars new-vars)) |
| 74 | + :bindings result})) |
| 75 | + |
| 76 | +(defn project |
| 77 | + [selected-vars result] |
| 78 | + (->> (:bindings result) |
| 79 | + (map #(zipmap (:vars result) %)) |
| 80 | + (map #(select-keys % selected-vars)))) |
| 81 | + |
| 82 | +(defn step |
| 83 | + [graph part-result pattern] |
| 84 | + (when-not (vector? pattern) |
| 85 | + (throw (ex-info "Pattern must be a vector" {:pattern pattern}))) |
| 86 | + (if (list? (first pattern)) |
| 87 | + (do-filter part-result pattern) |
| 88 | + (join graph part-result pattern))) |
| 89 | + |
| 90 | +(def identity-binding {:vars [] :bindings [[]]}) |
| 91 | + |
| 92 | +(defn query->map |
| 93 | + "Takes a string, seq, or map, and returns a map of :find and :where" |
| 94 | + [query] |
| 95 | + (let [query (if (string? query) (edn/read-string query) query)] |
| 96 | + (if (map? query) |
| 97 | + query |
| 98 | + (let [find-clause (take-while #(not= :where %) query) |
| 99 | + where-clause (drop-while #(not= :where %) query)] |
| 100 | + (when (or (not= :find (first find-clause)) |
| 101 | + (not= :where (first where-clause))) |
| 102 | + (throw (ex-info "Query must start with :find and :where" {:query query}))) |
| 103 | + {:find (rest find-clause) |
| 104 | + :where (rest where-clause)})))) |
| 105 | + |
| 106 | +(defn q |
| 107 | + [query graph] |
| 108 | + (let [{:keys [find where]} (query->map query)] |
| 109 | + (->> (reduce (partial step graph) identity-binding where) |
| 110 | + (project find)))) |
| 111 | + |
| 112 | +(comment |
| 113 | + ;; Load the starwars graph |
| 114 | + (def data (load-data "../../graphs/starwars.edn")) |
| 115 | + |
| 116 | + ;; [:find ?name ?color :where [?character :name ?name] [?character :color ?color] [(str/starts-with ?name "Darth")]] |
| 117 | + (def name-pattern '[?character :name ?name]) |
| 118 | + (def color-pattern '[?character :color ?color]) |
| 119 | + (def filter-pattern '[(str/starts-with? ?name "Darth")]) |
| 120 | + (def first-step (join data identity-binding name-pattern)) |
| 121 | + (def second-step (join data first-step color-pattern)) |
| 122 | + (def last-step (do-filter second-step filter-pattern)) |
| 123 | + (project '[?name ?color] last-step) |
| 124 | + |
| 125 | + (def name-color-query '[:find ?name ?color |
| 126 | + :where [?character :name ?name] |
| 127 | + [?character :color ?color] |
| 128 | + [(str/starts-with? ?name "Darth")]]) |
| 129 | + |
| 130 | + (q name-color-query data) |
| 131 | +) |
0 commit comments