-
Notifications
You must be signed in to change notification settings - Fork 188
Add CellIndex intersection #98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
bf8f857
ca5e2cb
3243874
ab64de4
d40ce07
3cea372
91f0341
1b4d3d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,7 +34,7 @@ type cellIndexNode struct { | |
// newCellIndexNode returns a node with the appropriate default values. | ||
func newCellIndexNode() cellIndexNode { | ||
return cellIndexNode{ | ||
cellID: 0, | ||
cellID: CellID(0), | ||
label: cellIndexDoneContents, | ||
parent: -1, | ||
} | ||
|
@@ -49,15 +49,52 @@ type rangeNode struct { | |
contents int32 // Contents of this node (an index within the cell tree). | ||
} | ||
|
||
type labels []int32 | ||
|
||
func (l *labels) Normalize() { | ||
encountered := make(map[int32]struct{}) | ||
|
||
for i := range *l { | ||
encountered[(*l)[i]] = struct{}{} | ||
} | ||
|
||
*l = (*l)[:0] | ||
for key := range encountered { | ||
*l = append(*l, key) | ||
} | ||
} | ||
|
||
type cellVisitor func(cellID CellID, label int32) bool | ||
|
||
// CellIndexIterator is an iterator that visits the entire set of indexed | ||
// (CellID, label) pairs in an unspecified order. | ||
type CellIndexIterator struct { | ||
// TODO(roberts): Implement | ||
nodes []cellIndexNode | ||
pos int | ||
} | ||
|
||
// NewCellIndexIterator creates an iterator for the given CellIndex. | ||
func NewCellIndexIterator(index *CellIndex) *CellIndexIterator { | ||
return &CellIndexIterator{} | ||
return &CellIndexIterator{ | ||
nodes: index.cellTree, | ||
pos: 0, | ||
} | ||
} | ||
|
||
func (c *CellIndexIterator) CellID() CellID { | ||
return c.nodes[c.pos].cellID | ||
} | ||
|
||
func (c *CellIndexIterator) Label() int32 { | ||
return c.nodes[c.pos].label | ||
} | ||
|
||
func (c *CellIndexIterator) Done() bool { | ||
return c.pos == len(c.nodes) | ||
} | ||
|
||
func (c *CellIndexIterator) Next() { | ||
c.pos++ | ||
} | ||
|
||
// CellIndexRangeIterator is an iterator that seeks and iterates over a set of | ||
|
@@ -202,7 +239,6 @@ func (c *CellIndexRangeIterator) Seek(target CellID) { | |
|
||
// Nonempty needs to find the next non-empty entry. | ||
for c.nonEmpty && c.IsEmpty() && !c.Done() { | ||
// c.Next() | ||
c.pos++ | ||
} | ||
} | ||
|
@@ -246,7 +282,7 @@ type CellIndexContentsIterator struct { | |
func NewCellIndexContentsIterator(index *CellIndex) *CellIndexContentsIterator { | ||
it := &CellIndexContentsIterator{ | ||
cellTree: index.cellTree, | ||
prevStartID: 0, | ||
prevStartID: CellID(0), | ||
nodeCutoff: -1, | ||
nextNodeCutoff: -1, | ||
node: cellIndexNode{label: cellIndexDoneContents}, | ||
|
@@ -256,7 +292,7 @@ func NewCellIndexContentsIterator(index *CellIndex) *CellIndexContentsIterator { | |
|
||
// Clear clears all state with respect to which range(s) have been visited. | ||
func (c *CellIndexContentsIterator) Clear() { | ||
c.prevStartID = 0 | ||
c.prevStartID = CellID(0) | ||
c.nodeCutoff = -1 | ||
c.nextNodeCutoff = -1 | ||
c.node.label = cellIndexDoneContents | ||
|
@@ -482,7 +518,8 @@ func (c *CellIndex) Build() { | |
c.cellTree = append(c.cellTree, cellIndexNode{ | ||
cellID: deltas[i].cellID, | ||
label: deltas[i].label, | ||
parent: contents}) | ||
parent: contents, | ||
}) | ||
contents = int32(len(c.cellTree) - 1) | ||
} else if deltas[i].cellID == SentinelCellID { | ||
contents = c.cellTree[contents].parent | ||
|
@@ -492,7 +529,48 @@ func (c *CellIndex) Build() { | |
} | ||
} | ||
|
||
// TODO(roberts): Differences from C++ | ||
// IntersectingLabels | ||
// VisitIntersectingCells | ||
// CellIndexIterator | ||
func (c *CellIndex) IntersectingLabels(target CellUnion) []int32 { | ||
var labels labels | ||
c.VisitIntersectingCells(target, func(cellID CellID, label int32) bool { | ||
labels = append(labels, label) | ||
return true | ||
}) | ||
|
||
labels.Normalize() | ||
return labels | ||
} | ||
|
||
func (c *CellIndex) VisitIntersectingCells(target CellUnion, visitor cellVisitor) bool { | ||
if len(target) == 0 { | ||
return true | ||
} | ||
|
||
pos := 0 | ||
contents := NewCellIndexContentsIterator(c) | ||
rangeIter := NewCellIndexRangeIterator(c) | ||
rangeIter.Begin() | ||
|
||
for ok := true; ok; ok = pos != len(target) { | ||
if rangeIter.LimitID() <= target[pos].RangeMin() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In C++ this was if rangeIter.LimitID() <= rangeIter.RangeMin() { is there a reason it's not like that here? |
||
rangeIter.Seek(target[pos].RangeMin()) | ||
} | ||
|
||
for ; rangeIter.StartID() <= target[pos].RangeMax(); rangeIter.Next() { | ||
for contents.StartUnion(rangeIter); !contents.Done(); contents.Next() { | ||
if !visitor(contents.CellID(), contents.Label()) { | ||
return false | ||
} | ||
} | ||
} | ||
|
||
pos++ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. keep the C++ comments on this block as well |
||
if pos != len(target) && target[pos].RangeMax() < rangeIter.StartID() { | ||
pos = target.lowerBound(pos+1, len(target), rangeIter.StartID()) | ||
if target[pos-1].RangeMax() >= rangeIter.StartID() { | ||
pos-- | ||
} | ||
} | ||
} | ||
|
||
return true | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,7 +49,6 @@ func cellIndexNodesEqual(a, b []cellIndexNode) bool { | |
return b[i].less(b[j]) | ||
}) | ||
return reflect.DeepEqual(a, b) | ||
|
||
} | ||
|
||
// copyCellIndexNodes creates a copy of the nodes so that sorting and other tests | ||
|
@@ -338,9 +337,108 @@ func TestCellIndexRandomCellUnions(t *testing.T) { | |
cellIndexQuadraticValidate(t, "Random Cell Unions", index, nil) | ||
} | ||
|
||
func TestCellIndexIntersectionOptimization(t *testing.T) { | ||
type cellIndexTestInput struct { | ||
cellID string | ||
label int32 | ||
} | ||
tests := []struct { | ||
label string | ||
have []cellIndexTestInput | ||
}{ | ||
{ | ||
// Tests various corner cases for the binary search optimization in | ||
// VisitIntersectingCells. | ||
label: "Intersection Optimization", | ||
have: []cellIndexTestInput{ | ||
{"1/001", 1}, | ||
{"1/333", 2}, | ||
{"2/00", 3}, | ||
{"2/0232", 4}, | ||
}, | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
index := &CellIndex{} | ||
for _, v := range test.have { | ||
index.Add(CellIDFromString(v.cellID), v.label) | ||
} | ||
index.Build() | ||
checkIntersection(t, test.label, makeCellUnion("1/010", "1/3"), index) | ||
checkIntersection(t, test.label, makeCellUnion("2/010", "2/011", "2/02"), index) | ||
} | ||
} | ||
|
||
func TestCellIndexIntersectionRandomCellUnions(t *testing.T) { | ||
// Construct cell unions from random CellIDs at random levels. Note that | ||
// because the cell level is chosen uniformly, there is a very high | ||
// likelihood that the cell unions will overlap. | ||
index := &CellIndex{} | ||
for i := int32(0); i < 100; i++ { | ||
index.AddCellUnion(randomCellUnion(10), i) | ||
} | ||
index.Build() | ||
for i := 0; i < 200; i++ { | ||
checkIntersection(t, "", randomCellUnion(10), index) | ||
} | ||
} | ||
|
||
func TestCellIndexIntersectionSemiRandomCellUnions(t *testing.T) { | ||
for i := 0; i < 200; i++ { | ||
index := &CellIndex{} | ||
id := CellIDFromString("1/0123012301230123") | ||
var target CellUnion | ||
for j := 0; j < 100; j++ { | ||
switch { | ||
case oneIn(10): | ||
index.Add(id, int32(j)) | ||
case oneIn(4): | ||
target = append(target, id) | ||
case oneIn(2): | ||
id = id.NextWrap() | ||
case oneIn(6) && !id.isFace(): | ||
id = id.immediateParent() | ||
case oneIn(6) && !id.IsLeaf(): | ||
id = id.ChildBegin() | ||
} | ||
} | ||
target.Normalize() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The call to Normalize wasn't in the C++. Does removing this change the test outcomes? |
||
index.Build() | ||
checkIntersection(t, "", target, index) | ||
} | ||
} | ||
|
||
func checkIntersection(t *testing.T, desc string, target CellUnion, index *CellIndex) { | ||
var expected, actual []int32 | ||
for it := NewCellIndexIterator(index); !it.Done(); it.Next() { | ||
if target.IntersectsCellID(it.CellID()) { | ||
expected = append(expected, it.Label()) | ||
} | ||
} | ||
|
||
index.VisitIntersectingCells(target, func(cellID CellID, label int32) bool { | ||
actual = append(actual, label) | ||
return true | ||
}) | ||
|
||
if !labelsEqual(actual, expected) { | ||
t.Errorf("%s: labels not equal but should be. %v != %v", desc, actual, expected) | ||
} | ||
} | ||
|
||
func labelsEqual(a, b []int32) bool { | ||
sort.Slice(a, func(i, j int) bool { | ||
return a[i] < a[j] | ||
}) | ||
sort.Slice(b, func(i, j int) bool { | ||
return b[i] < b[j] | ||
}) | ||
return reflect.DeepEqual(a, b) | ||
} | ||
|
||
// TODO(roberts): Differences from C++ | ||
// | ||
// Add remainder of TestCellIndexContentsIteratorSuppressesDuplicates | ||
// | ||
// additional Iterator related parts | ||
// Intersections related |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add the method comments from the C++