Skip to content
Permalink
Browse files

Added traverse and transitive-clojure functions

  • Loading branch information...
ericdscott committed Dec 15, 2018
1 parent 03df397 commit 125a1e3863d41735fffa5c8d458677bd33bdea9d
Showing with 119 additions and 6 deletions.
  1. +6 −2 CHANGELOG.md
  2. +36 −2 README.md
  3. +1 −1 project.clj
  4. +49 −0 src/igraph/core.clj
  5. +12 −1 test/igraph/core_test.clj
  6. +15 −0 test/igraph/graph_test.clj
@@ -2,10 +2,14 @@
All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/).


##[0.1.1] - 2018-12-9
## [0.1.1] - 2018-12-9
### Added
- Subtraction functions to the IGraph protocol, with support in Graph

##[0.1.2] - 2018-12-14
## [0.1.2] - 2018-12-14
### Added
- A new protocol ISet for basic set operations, with support in Graph

## [0.1.3] - 2018-12-14
### Added
- A traverse function, with a utility to define transitive-closure.
@@ -16,14 +16,14 @@ This is deployed to [clojars](https://clojars.org/ont-app/igraph):

### IGraph
The `IGraph` protocol specifies the following functions:
#### member access
#### Member access
- `(normal-form g)` -> {s {p #{o...}...}...}
- `(subjects g)` -> (s ...), a collection of subjects
- `(get-p-o g s)` -> {p #{o...} ...}
- `(get-o g s p)` -> #{o ...}
- `(ask g s p o)` -> truthy
- `(query g q)` -> collection of {var value ...} maps
#### membership changes
#### Membership changes
- `(read-only? g)` -> true if changing membership throws an exception
- `(add g to-add)` -> new graph with <i>to-add</i> present
- `(subtract g to-subtract)` -> new graph with <i>to-subtract</i> absent
@@ -34,6 +34,18 @@ Also `invoke` to support `IFn` as follows
- `(g s p)` = `(get-o g s p)`
- `(g s p o)` = `(ask g s p o)`


#### Traversal

- `(traverse g traversal acc to-visit)` -> acc, traversing `g` per `traversal`, starting with `to-visit`.
- `(trasitive-closure p) -> (fn [g acc to-visit] ...) -> [acc' to visit'], a traversal argument to `traverse`.
See the example in the `Graph` section below.
#### utilities
- `(normal-form? m)` -> true iff m is a map in normal form.
The [source file](https://github.com/ont-app/igraph/blob/master/src/igraph/core.clj) has fairly explicit docstrings.
### ISet
@@ -148,6 +160,28 @@ Querying is done with a very simple graph pattern using keywords starting with ?
{:?type :food, :?likee :meat, :?liker :john}}
```
Traversal is done with a function that returns the accumulator and a possibly empty list of nodes in the graph still to visit...
```
(def g (add (make-graph) [[:a :isa :b]
[:b :isa :c]
[:c :isa :d]]))

(defn isa* [g acc to-visit]
[(conj acc (first to-visit))
(concat to-visit (g (first to-visit) :isa))])

(traverse g isa* [] [:a])
->
[:a :b :c :d]
```
The isa* function defined above is equivalent to `transitive-closure`:
```
(traverse g (transitive-closure :isa) [] [:a])
->
[:a :b :c :d]
```
One subtracts from it like this (also returns new immutable object):
```
@@ -1,4 +1,4 @@
(defproject ont-app/igraph "0.1.2"
(defproject ont-app/igraph "0.1.3"
:description "Defines a IGraph protocol for maintaining a querying different graph implementations"
:url "https://github.com/ont-app/igraph"
:license {:name "Eclipse Public License"
@@ -123,3 +123,52 @@ Where
"Returns an IGraph whose normal form contains all statements in g1 not present in g2."
)
)


;;;;;;;;;;;;;;;
;;; TRAVERSAL
;;;;;;;;;;;;;;;;

(defn traverse
"Returns `acc` acquired by applying `traversal` to `g`starting with `to-visit`, skipping members in `history`.
Where
<acc> is an arbitrary clojure object
<traversal> := (fn [g acc to-visit]...) -> [<acc'> <to-visit'>]
<g> is a graph
<to-visit> := [<node> ...]
<history> := #{<visited node> ...}, this is conj'd with each call.
<node> is typically an element in <g>
<visited-node> is a node visited upstream. We filter these out to
avoid cycles.
"
([g traversal acc to-visit]
(traverse g traversal acc to-visit #{}))

([g traversal acc to-visit history]
(if (or (nil? to-visit)
(empty? to-visit))
;; nothing more to visit...
acc
;; else we keep going
(if (history (first to-visit))
(recur g traversal acc (rest to-visit) history)
;;else we're not in a cycle
(let [[acc to-visit-next] (traversal g acc to-visit)]
(recur g
traversal
acc
to-visit-next
(conj history (first to-visit))))))))

(defn transitive-closure [p]
"Returns <traversal> for chains of `p`.
Where
<traversal> := (fn [g acc to-visit]...) -> [<acc'> <to-visit'>],
s.t. <to-visit'> conj's all <o> s.t. (g <s> <p> <o>).
A traversal function argument for the `traverse` function .
<p> is a predicate, typcially an element of <g>
<g> is a graph.
"
(fn [g acc to-visit]
[(conj acc (first to-visit))
(concat to-visit (g (first to-visit) p))]))
@@ -2,4 +2,15 @@
(:require [clojure.test :refer :all]
[igraph.core :refer :all]))

;; No tests at this time see igraph.graph
(deftest traverse-test
(testing "Test traverse and transitive-closure"
(let [g (add (make-graph) [[:a :isa :b] [:b :isa :c][:c :isa :d]])
isa* (fn [g acc to-visit]
[(conj acc (first to-visit))
(concat to-visit (g (first to-visit) :isa))])
]
(is (= (traverse g isa* [] [:a])
[:a :b :c :d]))
;; transitive-closure writes equivalent of isa*...
(is (= (traverse g (transitive-closure :isa) [] [:a])
[:a :b :c :d])))))
@@ -135,3 +135,18 @@
[[:a :d :e]
[:f :g :h :i :j]]))))
)))

(deftest traverse-test
(testing "Test traverse and transitive-closure"
(let [g (add (make-graph) [[:a :isa :b] [:b :isa :c][:c :isa :d]])
isa* (fn [g acc to-visit]
[(conj acc (first to-visit))
(concat to-visit (g (first to-visit) :isa))])
]
(is (= (traverse g isa* [] [:a])
[:a :b :c :d]))
;; transitive-closure writes equivalent of isa*...
(is (= (traverse g (transitive-closure :isa) [] [:a])
[:a :b :c :d])))))


0 comments on commit 125a1e3

Please sign in to comment.
You can’t perform that action at this time.