There are two solutions. The first is to not require the containers to be a subclass of some generic superclass, but be converted to one (using the implicit function arguments). If the container is already a subclass of the required type, there is a predefined identity transformation that only returns it.
import collection.mutable.Builder import collection.TraversableLike import collection.generic.CanBuildFrom import collection.mutable.SeqLike class Cartesian[T, ST[T], TT[S]](val ll: TT[ST[T]])(implicit cbf: CanBuildFrom[Nothing, T, ST[T]], seqLike: ST[T] => SeqLike[T, ST[T]], traversableLike: TT[ST[T]] => TraversableLike[ST[T], TT[ST[T]]] ) extends Iterator[ST[T]] { def combicount (): Int = (1 /: ll) (_ * _.length) val last = combicount - 1 var iter = 0 override def hasNext (): Boolean = iter < last override def next (): ST[T] = { val res = combination (ll, iter, cbf()) iter += 1 res } def combination (xx: TT[ST[T]], i: Int, builder: Builder[T, ST[T]]): ST[T] = if (xx.isEmpty) builder.result else combination (xx.tail, i / xx.head.length, builder += xx.head (i % xx.head.length) ) }
This type of work:
scala> new Cartesian[String, Vector, Vector](Vector(Vector("a"), Vector("xy"), Vector("AB"))) res0: Cartesian[String,Vector,Vector] = empty iterator scala> new Cartesian[String, Array, Array](Array(Array("a"), Array("xy"), Array("AB"))) res1: Cartesian[String,Array,Array] = empty iterator
I needed to explicitly pass types due to an error https://issues.scala-lang.org/browse/SI-3343
It should be noted that this is better than using existential types, because calling next on the iterator returns the correct type, not Seq [Any].
There are several drawbacks here:
- If the container is not a subclass of the required type, it is converted to one, which costs performance
- The algorithm is not completely general. We need types that need to be converted to SeqLike or TraversableLike, only to use the subset of the functionality offered by these types. Therefore, conversion can be difficult.
- What if some features can be interpreted differently in different contexts? For example, a rectangle has two length properties (width and height)
Now for an alternative solution. We note that we really do not care about the types of collections, but only about their capabilities:
- TT must have
foldLeft , get(i: Int) (to get head / tail) - ST must have
length , get(i: Int) and Builder
So we can encode them:
trait HasGet[T, CC[_]] { def get(cc: CC[T], i: Int): T } object HasGet { implicit def seqLikeHasGet[T, CC[X] <: SeqLike[X, _]] = new HasGet[T, CC] { def get(cc: CC[T], i: Int): T = cc(i) } implicit def arrayHasGet[T] = new HasGet[T, Array] { def get(cc: Array[T], i: Int): T = cc(i) } } trait HasLength[CC] { def length(cc: CC): Int } object HasLength { implicit def seqLikeHasLength[CC <: SeqLike[_, _]] = new HasLength[CC] { def length(cc: CC) = cc.length } implicit def arrayHasLength[T] = new HasLength[Array[T]] { def length(cc: Array[T]) = cc.length } } trait HasFold[T, CC[_]] { def foldLeft[A](cc: CC[T], zero: A)(op: (A, T) => A): A } object HasFold { implicit def seqLikeHasFold[T, CC[X] <: SeqLike[X, _]] = new HasFold[T, CC] { def foldLeft[A](cc: CC[T], zero: A)(op: (A, T) => A): A = cc.foldLeft(zero)(op) } implicit def arrayHasFold[T] = new HasFold[T, Array] { def foldLeft[A](cc: Array[T], zero: A)(op: (A, T) => A): A = { var i = 0 var result = zero while (i < cc.length) { result = op(result, cc(i)) i += 1 } result } } }
(strictly speaking, HasFold is not required, since its implementation is in terms of length and get, but I added it here to make the algorithm translate more cleanly)
now the algorithm:
class Cartesian[T, ST[_], TT[Y]](val ll: TT[ST[T]])(implicit cbf: CanBuildFrom[Nothing, T, ST[T]], stHasLength: HasLength[ST[T]], stHasGet: HasGet[T, ST], ttHasFold: HasFold[ST[T], TT], ttHasGet: HasGet[ST[T], TT], ttHasLength: HasLength[TT[ST[T]]]) extends Iterator[ST[T]] { def combicount (): Int = ttHasFold.foldLeft(ll, 1)((a,l) => a * stHasLength.length(l)) val last = combicount - 1 var iter = 0 override def hasNext (): Boolean = iter < last override def next (): ST[T] = { val res = combination (ll, 0, iter, cbf()) iter += 1 res } def combination (xx: TT[ST[T]], j: Int, i: Int, builder: Builder[T, ST[T]]): ST[T] = if (ttHasLength.length(xx) == j) builder.result else { val head = ttHasGet.get(xx, j) val headLength = stHasLength.length(head) combination (xx, j + 1, i / headLength, builder += stHasGet.get(head, (i % headLength) )) } }
And use:
scala> new Cartesian[String, Vector, List](List(Vector("a"), Vector("xy"), Vector("AB"))) res6: Cartesian[String,Vector,List] = empty iterator scala> new Cartesian[String, Array, Array](Array(Array("a"), Array("xy"), Array("AB"))) res7: Cartesian[String,Array,Array] = empty iterator
Scalaz, probably all this is predetermined for you, unfortunately, I do not know very well.
(again I need to pass types because the output does not output the correct view)
The advantage is that the algorithm is now completely general and that there is no need for implicit conversions from the array to WrappedArray to make it work
Exercise: definition for tuples; -)