1 /***********************************************************************
2  * Copyright (c) 2013-2025 General Atomics Integrated Intelligence, Inc.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Apache License, Version 2.0
5  * which accompanies this distribution and is available at
6  * https://www.apache.org/licenses/LICENSE-2.0
7  ***********************************************************************/
8 
9 package org.locationtech.geomesa.utils.iterators
10 
11 /**
12  * Can create an iterator over all combinations of items from a list-of-lists.
13  * Because the final list of combinations can be large, we allow for a safe
14  * way to query the list size that is independent of the iterator itself.
15  * (That is, asking for the size does not exhaust any iterator.)
16  *
17  * NB:  The first sequence is the least significant; that is, it will
18  * increment fast while the last sequence is the most significant (will
19  * increment slowly).
20  *
21  * @param seqs the list-of-lists whose items are to be recombined
22  */
23 
24 case class CartesianProductIterable(seqs: Seq[Seq[_]]) extends Iterable[Seq[_]] {
25   lazy val expectedSize: Long = seqs.map(_.size.toLong).product
26 
27   def iterator: Iterator[Seq[_]] = new Iterator[Seq[_]] {
28     private val n: Int = seqs.size
29     private val maxes: Vector[Int] = seqs.map(seq => seq.size).toVector
30     private val indexes = Array.ofDim[Int](seqs.size)
31     private var nextItem: Seq[_] = if (isValid) realize else null
32 
33     def isValid: Boolean = (0 until n).forall(i => indexes(i) < maxes(i))
34 
35     def realize: Seq[_] = (0 until n).map(i => seqs(i)(indexes(i)))
36 
37     def hasNext: Boolean = nextItem != null
38 
39     def next(): Seq[_] = {
40       if (nextItem == null) throw new Exception("Iterator exhausted")
41       val result = nextItem
42 
43       // advance the internal state
44       nextItem = null
45       var j = 0
46       var done = false
47       while (j < n && !done) {
48         indexes(j) = indexes(j) + 1
49         if (indexes(j) >= maxes(j)) {
50           indexes(j) = 0
51           j = j + 1
52         } else {
53           done = true
54         }
55       }
56       if (done || j < n) nextItem = realize
57 
58       result
59     }
60   }
61 }
Line Stmt Id Pos Tree Symbol Tests Code
27 9231 1239 - 1242 Apply org.locationtech.geomesa.utils.iterators.CartesianProductIterable.$anon.<init> new $anon()
28 9175 1287 - 1296 Select scala.collection.SeqLike.size CartesianProductIterable.this.seqs.size
29 9176 1350 - 1358 Select scala.collection.SeqLike.size seq.size
29 9177 1342 - 1342 TypeApply scala.collection.Seq.canBuildFrom collection.this.Seq.canBuildFrom[Int]
29 9178 1334 - 1368 Select scala.collection.TraversableOnce.toVector CartesianProductIterable.this.seqs.map[Int, Seq[Int]](((seq: Seq[_]) => seq.size))(collection.this.Seq.canBuildFrom[Int]).toVector
30 9179 1412 - 1421 Select scala.collection.SeqLike.size CartesianProductIterable.this.seqs.size
30 9180 1395 - 1422 ApplyToImplicitArgs scala.Array.ofDim scala.Array.ofDim[Int](CartesianProductIterable.this.seqs.size)((ClassTag.Int: scala.reflect.ClassTag[Int]))
31 9181 1462 - 1469 Select org.locationtech.geomesa.utils.iterators.CartesianProductIterable.$anon.isValid $anon.this.isValid
31 9182 1471 - 1478 Select org.locationtech.geomesa.utils.iterators.CartesianProductIterable.$anon.realize $anon.this.realize
31 9183 1471 - 1478 Block org.locationtech.geomesa.utils.iterators.CartesianProductIterable.$anon.realize $anon.this.realize
31 9184 1484 - 1488 Literal <nosymbol> null
31 9185 1484 - 1488 Block <nosymbol> null
33 9186 1518 - 1519 Literal <nosymbol> 0
33 9187 1526 - 1527 Select org.locationtech.geomesa.utils.iterators.CartesianProductIterable.$anon.n $anon.this.n
33 9188 1554 - 1562 Apply scala.collection.immutable.Vector.apply $anon.this.maxes.apply(i)
33 9189 1541 - 1562 Apply scala.Int.< $anon.this.indexes.apply(i).<($anon.this.maxes.apply(i))
33 9190 1517 - 1563 Apply scala.collection.IterableLike.forall scala.Predef.intWrapper(0).until($anon.this.n).forall(((i: Int) => $anon.this.indexes.apply(i).<($anon.this.maxes.apply(i))))
35 9191 1592 - 1593 Literal <nosymbol> 0
35 9192 1600 - 1601 Select org.locationtech.geomesa.utils.iterators.CartesianProductIterable.$anon.n $anon.this.n
35 9193 1620 - 1630 Apply scala.Array.apply $anon.this.indexes.apply(i)
35 9194 1612 - 1631 Apply scala.collection.SeqLike.apply CartesianProductIterable.this.seqs.apply(i).apply($anon.this.indexes.apply(i))
35 9195 1606 - 1606 TypeApply scala.collection.immutable.IndexedSeq.canBuildFrom immutable.this.IndexedSeq.canBuildFrom[Any]
35 9196 1591 - 1632 ApplyToImplicitArgs scala.collection.TraversableLike.map scala.Predef.intWrapper(0).until($anon.this.n).map[Any, Seq[_]](((i: Int) => CartesianProductIterable.this.seqs.apply(i).apply($anon.this.indexes.apply(i))))(immutable.this.IndexedSeq.canBuildFrom[Any])
37 9197 1661 - 1677 Apply java.lang.Object.!= $anon.this.nextItem.!=(null)
40 9198 1716 - 1732 Apply java.lang.Object.== $anon.this.nextItem.==(null)
40 9199 1734 - 1775 Throw <nosymbol> throw new scala.`package`.Exception("Iterator exhausted")
40 9200 1734 - 1775 Block <nosymbol> throw new scala.`package`.Exception("Iterator exhausted")
40 9201 1712 - 1712 Literal <nosymbol> ()
40 9202 1712 - 1712 Block <nosymbol> ()
41 9203 1795 - 1803 Select org.locationtech.geomesa.utils.iterators.CartesianProductIterable.$anon.nextItem $anon.this.nextItem
44 9204 1847 - 1862 Apply org.locationtech.geomesa.utils.iterators.CartesianProductIterable.$anon.nextItem_= $anon.this.nextItem_=(null)
45 9205 1877 - 1878 Literal <nosymbol> 0
46 9206 1896 - 1901 Literal <nosymbol> false
47 9207 1919 - 1920 Select org.locationtech.geomesa.utils.iterators.CartesianProductIterable.$anon.n $anon.this.n
47 9208 1924 - 1929 Select scala.Boolean.unary_! done.unary_!
47 9209 1915 - 1929 Apply scala.Boolean.&& j.<($anon.this.n).&&(done.unary_!)
47 9219 1931 - 1931 Apply org.locationtech.geomesa.utils.iterators.CartesianProductIterable.$anon.while$1 while$1()
47 9220 1931 - 2108 Block <nosymbol> { { $anon.this.indexes.update(j, $anon.this.indexes.apply(j).+(1)); if ($anon.this.indexes.apply(j).>=($anon.this.maxes.apply(j))) { $anon.this.indexes.update(j, 0); j = j.+(1) } else done = true }; while$1() }
47 9221 1908 - 1908 Literal <nosymbol> ()
47 9222 1908 - 1908 Block <nosymbol> ()
48 9210 1954 - 1968 Apply scala.Int.+ $anon.this.indexes.apply(j).+(1)
48 9211 1941 - 1968 Apply scala.Array.update $anon.this.indexes.update(j, $anon.this.indexes.apply(j).+(1))
49 9212 1995 - 2003 Apply scala.collection.immutable.Vector.apply $anon.this.maxes.apply(j)
49 9213 1981 - 2003 Apply scala.Int.>= $anon.this.indexes.apply(j).>=($anon.this.maxes.apply(j))
49 9216 2005 - 2061 Block <nosymbol> { $anon.this.indexes.update(j, 0); j = j.+(1) }
50 9214 2017 - 2031 Apply scala.Array.update $anon.this.indexes.update(j, 0)
51 9215 2046 - 2051 Apply scala.Int.+ j.+(1)
53 9217 2086 - 2090 Literal <nosymbol> true
53 9218 2079 - 2090 Assign <nosymbol> done = true
56 9223 2131 - 2132 Select org.locationtech.geomesa.utils.iterators.CartesianProductIterable.$anon.n $anon.this.n
56 9224 2127 - 2132 Apply scala.Int.< j.<($anon.this.n)
56 9225 2119 - 2132 Apply scala.Boolean.|| done.||(j.<($anon.this.n))
56 9226 2145 - 2152 Select org.locationtech.geomesa.utils.iterators.CartesianProductIterable.$anon.realize $anon.this.realize
56 9227 2134 - 2152 Apply org.locationtech.geomesa.utils.iterators.CartesianProductIterable.$anon.nextItem_= $anon.this.nextItem_=($anon.this.realize)
56 9228 2134 - 2152 Block org.locationtech.geomesa.utils.iterators.CartesianProductIterable.$anon.nextItem_= $anon.this.nextItem_=($anon.this.realize)
56 9229 2115 - 2115 Literal <nosymbol> ()
56 9230 2115 - 2115 Block <nosymbol> ()