diff --git a/graph/iterator.go b/graph/iterator.go index 0fdc5a5..2ca4426 100644 --- a/graph/iterator.go +++ b/graph/iterator.go @@ -166,7 +166,7 @@ type Description struct { } // A curried function that can generates a new iterator based on some prior iterator. -type MorphismFunc func(Iterator) Iterator +type ApplyMorphismFunc func(QuadStore, Iterator) Iterator type Nexter interface { // Next advances the iterator to the next value, which will then be available through diff --git a/graph/path/path.go b/graph/path/path.go index 3d13740..3847300 100644 --- a/graph/path/path.go +++ b/graph/path/path.go @@ -23,21 +23,24 @@ import ( type morphism struct { Name string Reversal func() morphism - Apply graph.MorphismFunc + Apply graph.ApplyMorphismFunc } type Path struct { stack []morphism - qs graph.QuadStore + qs graph.QuadStore // Optionally. A nil qs is equivalent to a morphism. +} + +func (p *Path) IsMorphism() bool { return p.qs == nil } + +func StartMorphism(nodes ...string) *Path { + return StartPath(nil, nodes...) } func StartPath(qs graph.QuadStore, nodes ...string) *Path { - if len(nodes) == 0 { - return PathFromIterator(qs, qs.NodesAllIterator()) - } return &Path{ stack: []morphism{ - isMorphism(qs, nodes...), + isMorphism(nodes...), }, qs: qs, } @@ -67,7 +70,7 @@ func (p *Path) Reverse() *Path { } func (p *Path) Is(nodes ...string) *Path { - p.stack = append(p.stack, isMorphism(p.qs, nodes...)) + p.stack = append(p.stack, isMorphism(nodes...)) return p } @@ -77,11 +80,11 @@ func (p *Path) Tag(tags ...string) *Path { } func (p *Path) Out(via ...interface{}) *Path { - p.stack = append(p.stack, outMorphism(p.qs, via...)) + p.stack = append(p.stack, outMorphism(via...)) return p } func (p *Path) In(via ...interface{}) *Path { - p.stack = append(p.stack, inMorphism(p.qs, via...)) + p.stack = append(p.stack, inMorphism(via...)) return p } @@ -95,6 +98,11 @@ func (p *Path) Or(path *Path) *Path { return p } +func (p *Path) Except(path *Path) *Path { + p.stack = append(p.stack, exceptMorphism(path)) + return p +} + func (p *Path) Follow(path *Path) *Path { p.stack = append(p.stack, followMorphism(path)) return p @@ -106,31 +114,44 @@ func (p *Path) FollowReverse(path *Path) *Path { } func (p *Path) BuildIterator() graph.Iterator { - f := p.MorphismFunc() - return f(p.qs.NodesAllIterator()) + if p.IsMorphism() { + panic("Building an iterator from a morphism. Bind a QuadStore with BuildIteratorOn(qs)") + } + return p.BuildIteratorOn(p.qs) } -func (p *Path) MorphismFunc() graph.MorphismFunc { - return func(it graph.Iterator) graph.Iterator { +func (p *Path) BuildIteratorOn(qs graph.QuadStore) graph.Iterator { + f := p.MorphismFunc() + return f(qs, qs.NodesAllIterator()) +} + +func (p *Path) MorphismFunc() graph.ApplyMorphismFunc { + return func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { i := it.Clone() for _, m := range p.stack { - i = m.Apply(i) + i = m.Apply(qs, i) } return i } } -func isMorphism(qs graph.QuadStore, nodes ...string) morphism { +func isMorphism(nodes ...string) morphism { return morphism{ "is", - func() morphism { return isMorphism(qs, nodes...) }, - func(it graph.Iterator) graph.Iterator { - fixed := qs.FixedIterator() - for _, n := range nodes { - fixed.Add(qs.ValueOf(n)) + func() morphism { return isMorphism(nodes...) }, + func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + var sub graph.Iterator + if len(nodes) == 0 { + sub = qs.NodesAllIterator() + } else { + fixed := qs.FixedIterator() + for _, n := range nodes { + fixed.Add(qs.ValueOf(n)) + } + sub = fixed } and := iterator.NewAnd() - and.AddSubIterator(fixed) + and.AddSubIterator(sub) and.AddSubIterator(it) return and }, @@ -141,7 +162,7 @@ func tagMorphism(tags ...string) morphism { return morphism{ "tag", func() morphism { return tagMorphism(tags...) }, - func(it graph.Iterator) graph.Iterator { + func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { for _, t := range tags { it.Tagger().Add(t) } @@ -149,21 +170,25 @@ func tagMorphism(tags ...string) morphism { }} } -func outMorphism(qs graph.QuadStore, via ...interface{}) morphism { - path := buildViaPath(qs, via...) +func outMorphism(via ...interface{}) morphism { return morphism{ "out", - func() morphism { return inMorphism(qs, via...) }, - inOutIterator(path, false), + func() morphism { return inMorphism(via...) }, + func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + path := buildViaPath(qs, via...) + return inOutIterator(path, it, false) + }, } } -func inMorphism(qs graph.QuadStore, via ...interface{}) morphism { - path := buildViaPath(qs, via...) +func inMorphism(via ...interface{}) morphism { return morphism{ "in", - func() morphism { return outMorphism(qs, via...) }, - inOutIterator(path, true), + func() morphism { return outMorphism(via...) }, + func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + path := buildViaPath(qs, via...) + return inOutIterator(path, it, true) + }, } } @@ -171,7 +196,7 @@ func iteratorMorphism(it graph.Iterator) morphism { return morphism{ "iterator", func() morphism { return iteratorMorphism(it) }, - func(subIt graph.Iterator) graph.Iterator { + func(_ graph.QuadStore, subIt graph.Iterator) graph.Iterator { and := iterator.NewAnd() and.AddSubIterator(it) and.AddSubIterator(subIt) @@ -184,8 +209,8 @@ func andMorphism(path *Path) morphism { return morphism{ "and", func() morphism { return andMorphism(path) }, - func(it graph.Iterator) graph.Iterator { - subIt := path.BuildIterator() + func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + subIt := path.BuildIteratorOn(qs) and := iterator.NewAnd() and.AddSubIterator(it) and.AddSubIterator(subIt) @@ -198,8 +223,8 @@ func orMorphism(path *Path) morphism { return morphism{ "or", func() morphism { return orMorphism(path) }, - func(it graph.Iterator) graph.Iterator { - subIt := path.BuildIterator() + func(qs graph.QuadStore, it graph.Iterator) graph.Iterator { + subIt := path.BuildIteratorOn(qs) and := iterator.NewOr() and.AddSubIterator(it) and.AddSubIterator(subIt) @@ -212,27 +237,40 @@ func followMorphism(path *Path) morphism { return morphism{ "follow", func() morphism { return followMorphism(path.Reverse()) }, - func(base graph.Iterator) graph.Iterator { + func(qs graph.QuadStore, base graph.Iterator) graph.Iterator { p := path.MorphismFunc() - return p(base) + return p(qs, base) }, } } -func inOutIterator(viaPath *Path, reverse bool) graph.MorphismFunc { - return func(base graph.Iterator) graph.Iterator { - in, out := quad.Subject, quad.Object - if reverse { - in, out = out, in - } - lto := iterator.NewLinksTo(viaPath.qs, base, in) - and := iterator.NewAnd() - and.AddSubIterator(iterator.NewLinksTo(viaPath.qs, viaPath.BuildIterator(), quad.Predicate)) - and.AddSubIterator(lto) - return iterator.NewHasA(viaPath.qs, and, out) +func exceptMorphism(path *Path) morphism { + return morphism{ + "except", + func() morphism { return exceptMorphism(path) }, + func(qs graph.QuadStore, base graph.Iterator) graph.Iterator { + subIt := path.BuildIteratorOn(qs) + notIt := iterator.NewNot(subIt, qs.NodesAllIterator()) + and := iterator.NewAnd() + and.AddSubIterator(base) + and.AddSubIterator(notIt) + return and + }, } } +func inOutIterator(viaPath *Path, it graph.Iterator, reverse bool) graph.Iterator { + in, out := quad.Subject, quad.Object + if reverse { + in, out = out, in + } + lto := iterator.NewLinksTo(viaPath.qs, it, in) + and := iterator.NewAnd() + and.AddSubIterator(iterator.NewLinksTo(viaPath.qs, viaPath.BuildIterator(), quad.Predicate)) + and.AddSubIterator(lto) + return iterator.NewHasA(viaPath.qs, and, out) +} + func buildViaPath(qs graph.QuadStore, via ...interface{}) *Path { if len(via) == 0 { return PathFromIterator(qs, qs.NodesAllIterator()) diff --git a/graph/path/path_test.go b/graph/path/path_test.go index d9d6c0d..87419f9 100644 --- a/graph/path/path_test.go +++ b/graph/path/path_test.go @@ -136,20 +136,30 @@ func testSet(qs graph.QuadStore) []test { }, { message: "follow", - path: StartPath(qs, "C").Follow(StartPath(qs).Out("follows").Out("follows")), + path: StartPath(qs, "C").Follow(StartMorphism().Out("follows").Out("follows")), expect: []string{"B", "F", "G"}, }, { message: "followR", - path: StartPath(qs, "F").FollowReverse(StartPath(qs).Out("follows").Out("follows")), + path: StartPath(qs, "F").FollowReverse(StartMorphism().Out("follows").Out("follows")), expect: []string{"A", "C", "D"}, }, { message: "is, tag, instead of FollowR", - path: StartPath(qs).Tag("first").Follow(StartPath(qs).Out("follows").Out("follows")).Is("F"), + path: StartPath(qs).Tag("first").Follow(StartMorphism().Out("follows").Out("follows")).Is("F"), expect: []string{"A", "C", "D"}, tag: "first", }, + { + message: "use Except to filter out a single vertex", + path: StartPath(qs, "A", "B").Except(StartPath(qs, "A")), + expect: []string{"B"}, + }, + { + message: "use chained Except", + path: StartPath(qs, "A", "B", "C").Except(StartPath(qs, "B")).Except(StartPath(qs, "A")), + expect: []string{"C"}, + }, } }