From bf8f857969929a0403503c3e02412464d0fb0f49 Mon Sep 17 00:00:00 2001 From: Ruben Poppe Date: Fri, 13 Jan 2023 13:53:07 +0100 Subject: [PATCH 01/10] fix randomCellUnion --- s2/s2_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/s2/s2_test.go b/s2/s2_test.go index 9cdd7d05..fc546990 100644 --- a/s2/s2_test.go +++ b/s2/s2_test.go @@ -26,11 +26,9 @@ import ( "github.com/golang/geo/s1" ) -var ( - // To set in testing add "--benchmark_brute_force=true" to your test command. - benchmarkBruteForce = flag.Bool("benchmark_brute_force", false, - "When set, use brute force algorithms in benchmarking.") -) +// To set in testing add "--benchmark_brute_force=true" to your test command. +var benchmarkBruteForce = flag.Bool("benchmark_brute_force", false, + "When set, use brute force algorithms in benchmarking.") // float64Eq reports whether the two values are within the default epsilon. func float64Eq(x, y float64) bool { return float64Near(x, y, epsilon) } @@ -143,6 +141,7 @@ func randomCellUnion(n int) CellUnion { for i := 0; i < n; i++ { cu = append(cu, randomCellID()) } + cu.Normalize() return cu } From ca5e2cb8d7139a7d901cf4cd793ecf27deb8b5f3 Mon Sep 17 00:00:00 2001 From: Ruben Poppe Date: Fri, 13 Jan 2023 13:55:08 +0100 Subject: [PATCH 02/10] refactor --- s2/cell_index.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/s2/cell_index.go b/s2/cell_index.go index ef16d089..a884c79f 100644 --- a/s2/cell_index.go +++ b/s2/cell_index.go @@ -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, } @@ -202,7 +202,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 +245,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 +255,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 @@ -343,7 +342,7 @@ func (c *CellIndexContentsIterator) StartUnion(r *CellIndexRangeIterator) { // There is also a helper method that adds all elements of CellUnion with the // same label: // -// index.AddCellUnion(cellUnion, label) +// index.AddCellUnion(cellUnion, label) // // Note that the index is not dynamic; the contents of the index cannot be // changed once it has been built. Adding more after calling Build results in @@ -353,7 +352,7 @@ func (c *CellIndexContentsIterator) StartUnion(r *CellIndexRangeIterator) { // is to use a built-in method such as IntersectingLabels (which returns // the labels of all cells that intersect a given target CellUnion): // -// labels := index.IntersectingLabels(targetUnion); +// labels := index.IntersectingLabels(targetUnion); // // Alternatively, you can use a ClosestCellQuery which computes the cell(s) // that are closest to a given target geometry. @@ -482,7 +481,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 From 3243874f674ed4441a33520e090d5589ae235d63 Mon Sep 17 00:00:00 2001 From: Ruben Poppe Date: Fri, 13 Jan 2023 14:00:02 +0100 Subject: [PATCH 03/10] add cellIndexIterator --- s2/cell_index.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/s2/cell_index.go b/s2/cell_index.go index a884c79f..cd9b23ca 100644 --- a/s2/cell_index.go +++ b/s2/cell_index.go @@ -52,12 +52,32 @@ type rangeNode struct { // 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 @@ -495,4 +515,3 @@ func (c *CellIndex) Build() { // TODO(roberts): Differences from C++ // IntersectingLabels // VisitIntersectingCells -// CellIndexIterator From ab64de45d31209e5f135078c787c111fb8584683 Mon Sep 17 00:00:00 2001 From: Ruben Poppe Date: Fri, 13 Jan 2023 14:36:35 +0100 Subject: [PATCH 04/10] add cellIndex intersection --- s2/cell_index.go | 66 +++++++++++++++++++++++++++ s2/cell_index_test.go | 102 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 166 insertions(+), 2 deletions(-) diff --git a/s2/cell_index.go b/s2/cell_index.go index cd9b23ca..5ce4fff8 100644 --- a/s2/cell_index.go +++ b/s2/cell_index.go @@ -49,6 +49,26 @@ type rangeNode struct { contents int32 // Contents of this node (an index within the cell tree). } +type labels []int32 + +func (l *labels) Normalize() { + if len(*l) == 0 { + return + } + labels := *l + sort.Slice(labels, func(i, j int) bool { return labels[i] < labels[j] }) + + var lastIndex int + for i := 0; i < len(*l); i++ { + if labels[lastIndex] != labels[i] { + lastIndex++ + labels[lastIndex] = labels[i] + } + } +} + +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 { @@ -512,6 +532,52 @@ func (c *CellIndex) Build() { } } +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() { + 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++ + 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 +} + // TODO(roberts): Differences from C++ // IntersectingLabels // VisitIntersectingCells diff --git a/s2/cell_index_test.go b/s2/cell_index_test.go index aae2e859..a11f86e2 100644 --- a/s2/cell_index_test.go +++ b/s2/cell_index_test.go @@ -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() + 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 From d40ce0707a3302b28edb409a4c0fd302a41f0ae5 Mon Sep 17 00:00:00 2001 From: Ruben Poppe Date: Sat, 14 Jan 2023 13:54:42 +0100 Subject: [PATCH 05/10] remove comments --- s2/cell_index.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/s2/cell_index.go b/s2/cell_index.go index 5ce4fff8..359ece01 100644 --- a/s2/cell_index.go +++ b/s2/cell_index.go @@ -577,7 +577,3 @@ func (c *CellIndex) VisitIntersectingCells(target CellUnion, visitor cellVisitor return true } - -// TODO(roberts): Differences from C++ -// IntersectingLabels -// VisitIntersectingCells From 3cea3724f08c9094c1c0641d780c5aafcd0effca Mon Sep 17 00:00:00 2001 From: Ruben Poppe Date: Mon, 23 Jan 2023 09:23:45 +0100 Subject: [PATCH 06/10] fix labels normalize --- s2/cell_index.go | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/s2/cell_index.go b/s2/cell_index.go index 359ece01..afe33aa8 100644 --- a/s2/cell_index.go +++ b/s2/cell_index.go @@ -52,18 +52,15 @@ type rangeNode struct { type labels []int32 func (l *labels) Normalize() { - if len(*l) == 0 { - return + encountered := make(map[int32]struct{}) + + for i := range *l { + encountered[(*l)[i]] = struct{}{} } - labels := *l - sort.Slice(labels, func(i, j int) bool { return labels[i] < labels[j] }) - - var lastIndex int - for i := 0; i < len(*l); i++ { - if labels[lastIndex] != labels[i] { - lastIndex++ - labels[lastIndex] = labels[i] - } + + *l = (*l)[:0] + for key := range encountered { + *l = append(*l, key) } } From 1b4d3d78c356d40fb8f44d7aef146453454b96d7 Mon Sep 17 00:00:00 2001 From: Jesse Rosenstock Date: Tue, 1 Apr 2025 10:32:20 +0200 Subject: [PATCH 07/10] cell_index_test.go: Replace cellIDFromString with CellIDFromString --- s2/cell_index_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/s2/cell_index_test.go b/s2/cell_index_test.go index 40420c73..d8f01af6 100644 --- a/s2/cell_index_test.go +++ b/s2/cell_index_test.go @@ -362,7 +362,7 @@ func TestCellIndexIntersectionOptimization(t *testing.T) { for _, test := range tests { index := &CellIndex{} for _, v := range test.have { - index.Add(cellIDFromString(v.cellID), v.label) + index.Add(CellIDFromString(v.cellID), v.label) } index.Build() checkIntersection(t, test.label, makeCellUnion("1/010", "1/3"), index) @@ -387,7 +387,7 @@ func TestCellIndexIntersectionRandomCellUnions(t *testing.T) { func TestCellIndexIntersectionSemiRandomCellUnions(t *testing.T) { for i := 0; i < 200; i++ { index := &CellIndex{} - id := cellIDFromString("1/0123012301230123") + id := CellIDFromString("1/0123012301230123") var target CellUnion for j := 0; j < 100; j++ { switch { From a9cb85884f314902e3b3dbc441a0232d2dd47e2d Mon Sep 17 00:00:00 2001 From: Ruben Poppe Date: Tue, 30 Dec 2025 15:25:12 +0100 Subject: [PATCH 08/10] add comments --- s2/cell_index.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/s2/cell_index.go b/s2/cell_index.go index 719f8fe9..53bd7d9a 100644 --- a/s2/cell_index.go +++ b/s2/cell_index.go @@ -529,6 +529,8 @@ func (c *CellIndex) Build() { } } +// 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 { @@ -563,6 +565,10 @@ func (c *CellIndex) VisitIntersectingCells(target CellUnion, visitor cellVisitor } } + // 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++ if pos != len(target) && target[pos].RangeMax() < rangeIter.StartID() { pos = target.lowerBound(pos+1, len(target), rangeIter.StartID()) From bfb4b0dea5fc513804f3179315eb981de8228856 Mon Sep 17 00:00:00 2001 From: Ruben Poppe Date: Tue, 30 Dec 2025 15:48:21 +0100 Subject: [PATCH 09/10] mark cell index as implemented --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index afa923c9..f1dde08f 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ S2Cap | ✅ S2Cell | ✅ S2CellId | ✅ S2CellIdVector | ❌ -S2CellIndex | 🟡 +S2CellIndex | ✅ S2CellUnion | ✅ S2Coords | ✅ S2DensityTree | ❌ From 93f50415fe0dd3c7cbd65732334e03e093f43d2d Mon Sep 17 00:00:00 2001 From: Ruben Poppe Date: Wed, 31 Dec 2025 18:06:55 +0100 Subject: [PATCH 10/10] add test remainder --- s2/cell_index.go | 4 ++ s2/cell_index_test.go | 91 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/s2/cell_index.go b/s2/cell_index.go index 53bd7d9a..80cf5d5d 100644 --- a/s2/cell_index.go +++ b/s2/cell_index.go @@ -89,6 +89,10 @@ 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) } diff --git a/s2/cell_index_test.go b/s2/cell_index_test.go index d8f01af6..7e6a416f 100644 --- a/s2/cell_index_test.go +++ b/s2/cell_index_test.go @@ -48,7 +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) + + 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 @@ -60,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) { @@ -337,6 +341,61 @@ func TestCellIndexRandomCellUnions(t *testing.T) { cellIndexQuadraticValidate(t, "Random Cell Unions", index, nil) } +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 @@ -436,9 +495,3 @@ func labelsEqual(a, b []int32) bool { }) return reflect.DeepEqual(a, b) } - -// TODO(roberts): Differences from C++ -// -// Add remainder of TestCellIndexContentsIteratorSuppressesDuplicates -// -// additional Iterator related parts