-
Notifications
You must be signed in to change notification settings - Fork 191
cell_index: Add IntersectingLabels and VisitIntersectingCells #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
a9cb858
bfb4b0d
93f5041
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,56 @@ 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) Begin() { | ||
| c.pos = 0 | ||
| } | ||
|
|
||
| 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 +243,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 +286,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 +296,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 +522,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 +533,54 @@ func (c *CellIndex) Build() { | |
| } | ||
| } | ||
|
|
||
| // TODO(roberts): Differences from C++ | ||
| // IntersectingLabels | ||
| // VisitIntersectingCells | ||
| // CellIndexIterator | ||
| // IntersectingLabels is a convenience function that returns | ||
| // the labels of all indexed cells that intersect the given CellUnion "target". | ||
| 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() { | ||
|
Collaborator
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?
Author
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++ it was
Collaborator
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. |
||
| 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 | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Check whether the next target cell is also contained by the leaf cell | ||
| // range that we just processed. If so, we can skip over all such cells | ||
| // using binary search. This speeds up benchmarks by between 2x and 10x | ||
| // when the average number of intersecting cells is small (< 1). | ||
| pos++ | ||
rubenpoppe marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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 |
|---|---|---|
|
|
@@ -48,8 +48,14 @@ func cellIndexNodesEqual(a, b []cellIndexNode) bool { | |
| sort.Slice(b, func(i, j int) bool { | ||
| return b[i].less(b[j]) | ||
| }) | ||
| return reflect.DeepEqual(a, b) | ||
|
Author
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.
|
||
|
|
||
| for i := range a { | ||
| if a[i].cellID != b[i].cellID && a[i].label != b[i].label { | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| return true | ||
| } | ||
|
|
||
| // copyCellIndexNodes creates a copy of the nodes so that sorting and other tests | ||
|
|
@@ -61,19 +67,16 @@ func copyCellIndexNodes(in []cellIndexNode) []cellIndexNode { | |
| } | ||
|
|
||
| func verifyCellIndexCellIterator(t *testing.T, desc string, index *CellIndex) { | ||
| // TODO(roberts): Once the plain iterator is implemented, add this check. | ||
| /* | ||
| var actual []cellIndexNode | ||
| iter := NewCellIndexIterator(index) | ||
| for iter.Begin(); !iter.Done(); iter.Next() { | ||
| actual = append(actual, cellIndexNode{iter.StartID(), iter.Label()) | ||
| } | ||
| var actual []cellIndexNode | ||
| iter := NewCellIndexIterator(index) | ||
| for iter.Begin(); !iter.Done(); iter.Next() { | ||
| actual = append(actual, cellIndexNode{cellID: iter.CellID(), label: iter.Label()}) | ||
| } | ||
|
|
||
| want := copyCellIndexNodes(index.cellTree) | ||
| if !cellIndexNodesEqual(actual, want) { | ||
| t.Errorf("%s: cellIndexNodes not equal but should be. %v != %v", desc, actual, want) | ||
| } | ||
| */ | ||
| want := copyCellIndexNodes(index.cellTree) | ||
| if !cellIndexNodesEqual(actual, want) { | ||
| t.Errorf("%s: cellIndexNodes not equal but should be. %v != %v", desc, actual, want) | ||
| } | ||
| } | ||
|
|
||
| func verifyCellIndexRangeIterators(t *testing.T, desc string, index *CellIndex) { | ||
|
|
@@ -338,9 +341,157 @@ func TestCellIndexRandomCellUnions(t *testing.T) { | |
| cellIndexQuadraticValidate(t, "Random Cell Unions", index, nil) | ||
| } | ||
|
|
||
| // TODO(roberts): Differences from C++ | ||
| // | ||
| // Add remainder of TestCellIndexContentsIteratorSuppressesDuplicates | ||
| // | ||
| // additional Iterator related parts | ||
| // Intersections related | ||
| func expectContents(t *testing.T, target CellID, index *CellIndex, contents *CellIndexContentsIterator, expected []cellIndexNode) { | ||
| rangeIter := NewCellIndexRangeIterator(index) | ||
| rangeIter.Seek(target) | ||
|
|
||
| var actual []cellIndexNode | ||
| for contents.StartUnion(rangeIter); !contents.Done(); contents.Next() { | ||
| actual = append(actual, cellIndexNode{cellID: contents.CellID(), label: contents.Label()}) | ||
| } | ||
| if !cellIndexNodesEqual(expected, actual) { | ||
| t.Errorf("comparing contents iterator contents to this range: got %+v, want %+v", actual, expected) | ||
| } | ||
| } | ||
|
|
||
| func TestCellIndexContentsIteratorSuppressesDuplicates(t *testing.T) { | ||
| index := &CellIndex{} | ||
| index.Add(CellIDFromString("2/1"), 1) | ||
| index.Add(CellIDFromString("2/1"), 2) | ||
| index.Add(CellIDFromString("2/10"), 3) | ||
| index.Add(CellIDFromString("2/100"), 4) | ||
| index.Add(CellIDFromString("2/102"), 5) | ||
| index.Add(CellIDFromString("2/1023"), 6) | ||
| index.Add(CellIDFromString("2/31"), 7) | ||
| index.Add(CellIDFromString("2/313"), 8) | ||
| index.Add(CellIDFromString("2/3132"), 9) | ||
| index.Add(CellIDFromString("3/1"), 10) | ||
| index.Add(CellIDFromString("3/12"), 11) | ||
| index.Add(CellIDFromString("3/13"), 12) | ||
|
|
||
| cellIndexQuadraticValidate(t, "Suppress Duplicates", index, nil) | ||
|
|
||
| contents := NewCellIndexContentsIterator(index) | ||
| expectContents(t, CellIDFromString("1/123"), index, contents, []cellIndexNode{}) | ||
| expectContents(t, CellIDFromString("2/100123"), index, contents, []cellIndexNode{{cellID: CellIDFromString("2/1"), label: 1}, {cellID: CellIDFromString("2/1"), label: 2}, {cellID: CellIDFromString("2/10"), label: 3}, {cellID: CellIDFromString("2/100"), label: 4}}) | ||
| // Check that a second call with the same key yields no additional results. | ||
| expectContents(t, CellIDFromString("2/100123"), index, contents, []cellIndexNode{}) | ||
| // Check that seeking to a different branch yields only the new values. | ||
| expectContents(t, CellIDFromString("2/10232"), index, contents, []cellIndexNode{{cellID: CellIDFromString("2/102"), label: 5}, {cellID: CellIDFromString("2/1023"), label: 6}}) | ||
| // Seek to a node with a different root. | ||
| expectContents(t, CellIDFromString("2/313"), index, contents, []cellIndexNode{{cellID: CellIDFromString("2/31"), label: 7}, {cellID: CellIDFromString("2/313"), label: 8}}) | ||
| // Seek to a descendant of the previous node. | ||
| expectContents(t, CellIDFromString("2/3132333"), index, contents, []cellIndexNode{{cellID: CellIDFromString("2/3132"), label: 9}}) | ||
| // Seek to an ancestor of the previous node. | ||
| expectContents(t, CellIDFromString("2/213"), index, contents, []cellIndexNode{}) | ||
| // A few more tests of incremental reporting. | ||
| expectContents(t, CellIDFromString("3/1232"), index, contents, []cellIndexNode{{cellID: CellIDFromString("3/1"), label: 10}, {cellID: CellIDFromString("3/12"), label: 11}}) | ||
| expectContents(t, CellIDFromString("3/133210"), index, contents, []cellIndexNode{{cellID: CellIDFromString("3/13"), label: 12}}) | ||
| expectContents(t, CellIDFromString("3/133210"), index, contents, []cellIndexNode{}) | ||
| expectContents(t, CellIDFromString("5/0"), index, contents, []cellIndexNode{}) | ||
|
|
||
| // Now try moving backwards, which is expected to yield values that were | ||
| // already reported above. | ||
| expectContents(t, CellIDFromString("3/13221"), index, contents, []cellIndexNode{{cellID: CellIDFromString("3/1"), label: 10}, {cellID: CellIDFromString("3/13"), label: 12}}) | ||
| expectContents(t, CellIDFromString("2/31112"), index, contents, []cellIndexNode{{cellID: CellIDFromString("2/31"), label: 7}}) | ||
| } | ||
|
|
||
| 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() | ||
|
Collaborator
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?
Author
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. Removing
Collaborator
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. I think this is because |
||
| 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) | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.