cayley/graph/or_iterator.go
kortschak 60d5c60817 Canonicalise iterator receiver names
This apparently meaningless churn improves godoc readability.
2014-06-28 21:36:50 +09:30

287 lines
7.6 KiB
Go

// Copyright 2014 The Cayley Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package graph
// Defines the or and short-circuiting or iterator. Or is the union operator for it's subiterators.
// Short-circuiting-or is a little different. It will return values from the first iterator that returns
// values at all, and then stops.
//
// Never reorders the iterators from the order they arrive. It is either the union or the first one.
// May return the same value twice -- once for each branch.
import (
"container/list"
"fmt"
"strings"
)
type OrIterator struct {
BaseIterator
isShortCircuiting bool
internalIterators []Iterator
itCount int
currentIterator int
}
func NewOrIterator() *OrIterator {
var or OrIterator
BaseIteratorInit(&or.BaseIterator)
or.internalIterators = make([]Iterator, 0, 20)
or.isShortCircuiting = false
or.currentIterator = -1
return &or
}
func NewShortCircuitOrIterator() *OrIterator {
var or OrIterator
BaseIteratorInit(&or.BaseIterator)
or.internalIterators = make([]Iterator, 0, 20)
or.isShortCircuiting = true
or.currentIterator = -1
return &or
}
// Reset all internal iterators
func (it *OrIterator) Reset() {
for _, sub := range it.internalIterators {
sub.Reset()
}
it.currentIterator = -1
}
func (it *OrIterator) Clone() Iterator {
var or *OrIterator
if it.isShortCircuiting {
or = NewShortCircuitOrIterator()
} else {
or = NewOrIterator()
}
for _, sub := range it.internalIterators {
or.AddSubIterator(sub.Clone())
}
it.CopyTagsFrom(it)
return or
}
// Returns a list.List of the subiterators, in order.
func (it *OrIterator) GetSubIterators() *list.List {
l := list.New()
for _, sub := range it.internalIterators {
l.PushBack(sub)
}
return l
}
// Overrides BaseIterator TagResults, as it needs to add it's own results and
// recurse down it's subiterators.
func (it *OrIterator) TagResults(out *map[string]TSVal) {
it.BaseIterator.TagResults(out)
it.internalIterators[it.currentIterator].TagResults(out)
}
// DEPRECATED Returns the ResultTree for this iterator, recurses to it's subiterators.
func (it *OrIterator) GetResultTree() *ResultTree {
tree := NewResultTree(it.LastResult())
for _, sub := range it.internalIterators {
tree.AddSubtree(sub.GetResultTree())
}
return tree
}
// Prints information about this iterator.
func (it *OrIterator) DebugString(indent int) string {
var total string
for i, sub := range it.internalIterators {
total += strings.Repeat(" ", indent+2)
total += fmt.Sprintf("%d:\n%s\n", i, sub.DebugString(indent+4))
}
var tags string
for _, k := range it.Tags() {
tags += fmt.Sprintf("%s;", k)
}
spaces := strings.Repeat(" ", indent+2)
return fmt.Sprintf("%s(%s\n%stags:%s\n%sits:\n%s)",
strings.Repeat(" ", indent),
it.Type(),
spaces,
tags,
spaces,
total)
}
// Add a subiterator to this Or iterator. Order matters.
func (it *OrIterator) AddSubIterator(sub Iterator) {
it.internalIterators = append(it.internalIterators, sub)
it.itCount++
}
// Returns the Next value from the Or iterator. Because the Or is the
// union of its subiterators, it must produce from all subiterators -- unless
// it's shortcircuiting, in which case, it's the first one that returns anything.
func (it *OrIterator) Next() (TSVal, bool) {
NextLogIn(it)
var curr TSVal
var exists bool
firstTime := false
for {
if it.currentIterator == -1 {
it.currentIterator = 0
firstTime = true
}
curIt := it.internalIterators[it.currentIterator]
curr, exists = curIt.Next()
if !exists {
if it.isShortCircuiting && !firstTime {
return NextLogOut(it, nil, false)
}
it.currentIterator++
if it.currentIterator == it.itCount {
return NextLogOut(it, nil, false)
}
} else {
it.Last = curr
return NextLogOut(it, curr, true)
}
}
panic("Somehow broke out of Next() loop in OrIterator")
}
// Checks a value against the iterators, in order.
func (it *OrIterator) checkSubIts(val TSVal) bool {
var subIsGood = false
for i, sub := range it.internalIterators {
subIsGood = sub.Check(val)
if subIsGood {
it.currentIterator = i
break
}
}
return subIsGood
}
// Check a value against the entire iterator, in order.
func (it *OrIterator) Check(val TSVal) bool {
CheckLogIn(it, val)
anyGood := it.checkSubIts(val)
if !anyGood {
return CheckLogOut(it, val, false)
}
it.Last = val
return CheckLogOut(it, val, true)
}
// Returns the approximate size of the Or iterator. Because we're dealing
// with a union, we know that the largest we can be is the sum of all the iterators,
// or in the case of short-circuiting, the longest.
func (it *OrIterator) Size() (int64, bool) {
var val int64
var b bool
if it.isShortCircuiting {
val = 0
b = true
for _, sub := range it.internalIterators {
newval, newb := sub.Size()
if val < newval {
val = newval
}
b = newb && b
}
} else {
val = 0
b = true
for _, sub := range it.internalIterators {
newval, newb := sub.Size()
val += newval
b = newb && b
}
}
return val, b
}
// An Or has no NextResult of its own -- that is, there are no other values
// which satisfy our previous result that are not the result itself. Our
// subiterators might, however, so just pass the call recursively. In the case of
// shortcircuiting, only allow new results from the currently checked iterator
func (it *OrIterator) NextResult() bool {
if it.currentIterator != -1 {
return it.internalIterators[it.currentIterator].NextResult()
}
return false
}
// Perform or-specific cleanup, of which there currently is none.
func (it *OrIterator) cleanUp() {}
// Close this iterator, and, by extension, close the subiterators.
// Close should be idempotent, and it follows that if it's subiterators
// follow this contract, the And follows the contract.
func (it *OrIterator) Close() {
it.cleanUp()
for _, sub := range it.internalIterators {
sub.Close()
}
}
func (it *OrIterator) Optimize() (Iterator, bool) {
oldItList := it.GetSubIterators()
itList := optimizeSubIterators(oldItList)
// Close the replaced iterators (they ought to close themselves, but Close()
// is idempotent, so this just protects against any machinations).
closeIteratorList(oldItList, nil)
newOr := NewOrIterator()
newOr.isShortCircuiting = it.isShortCircuiting
// Add the subiterators in order.
for e := itList.Front(); e != nil; e = e.Next() {
newOr.AddSubIterator(e.Value.(Iterator))
}
// Move the tags hanging on us (like any good replacement).
newOr.CopyTagsFrom(it)
// And close ourselves but not our subiterators -- some may still be alive in
// the new And (they were unchanged upon calling Optimize() on them, at the
// start).
it.cleanUp()
return newOr, true
}
func (it *OrIterator) GetStats() *IteratorStats {
CheckCost := int64(0)
NextCost := int64(0)
Size := int64(0)
for _, sub := range it.internalIterators {
stats := sub.GetStats()
NextCost += stats.NextCost
CheckCost += stats.CheckCost
if it.isShortCircuiting {
if Size < stats.Size {
Size = stats.Size
}
} else {
Size += stats.Size
}
}
return &IteratorStats{
CheckCost: CheckCost,
NextCost: NextCost,
Size: Size,
}
}
// Register this as an "or" iterator.
func (it *OrIterator) Type() string { return "or" }