0

I am attempting to make a ScalaCheck matrix generator that will generate a 2D array/ matrix of a specified order (size/dim). I started with the example on the tutorial, and simplified it (for prototyping--I understand I should use sized to be able to resize future generators) to

def matrix[ T ]( g: Gen[ T ] )( order: Int ): Gen[ Array[ Seq[ T ] ] ] =
  Gen.listOfN( order, Gen.listOfN( order, g ) ).map(_.toArray)  

However, I could not get an Array[Array[T]] from this without putting in a ClassTag:

def matrix[ T : ClassTag]( g: Gen[ T ] )( order: Int ): Gen[ Array[ Seq[ T ] ] ] =
  Gen.listOfN( order, Gen.listOfN( order, g ) ).map(_.toArray)  

Which I sort-of understand--I'm not that experienced in Scala. In the process of getting there, I tried this:

def matrix2[ T : ClassTag]( g: Gen[ T ] )( order: Int ): Gen[ Array[ Array[ T ] ] ] =
  for {
    rowSeq <- Gen.listOfN( order, g )
    rowArray <- rowSeq.toArray
    seqOfRowArrays <- Gen.listOfN( order, rowArray)
    matrix <- seqOfRowArrays.toArray
  } yield matrix

but the compiler complained
"type mismatch; found : Array[T] required: org.scalacheck.Gen[Array[Array[T]]]"
on the rowArray line and
"Multiple markers at this line - type mismatch; found : org.scalacheck.Gen[Array[T]] required: scala.collection.GenTraversableOnce[?]"
on the seqOfRowArrays line.

My questions are:
1. Is there a simpler way to get a defined size Array[Array[T]], ie some way to bound Arbitrary?
2. What is wrong with the typing of the above for expression?
3. When tests fail, how will I keep ScalaCheck from reducing the order during shrinking?

E Bro
  • 33
  • 5

1 Answers1

0

#1: Don't worry about Arbitrary v. Gen. They are basically the same. There is a good answer explaining the distinctions between them here: Why do you need Arbitraries in scalacheck?

#2 You are mixing your monadic containers (Gen and Array) and scala is doing its best to combine them, and this gives you a confusing error.

To fix your type error, do this

def matrix2[ T : ClassTag]( g: Gen[ T ] )( order: Int ): Gen[ Array[ Array[ T ] ] ] =
  for {
    rowSeq <- Gen.listOfN( order, g )
    rowArray = rowSeq.toArray
    seqOfRowArrays <- Gen.listOfN( order, rowArray)
  } yield seqOfRowArrays.toArray

If you run this in the terminal you will notice something odd:

scala> matrix2(Gen.choose(1,10))(3)
res2: org.scalacheck.Gen[Array[Array[Int]]] = org.scalacheck.Gen$$anon$6@4c93778e

scala> res2.sample
res3: Option[Array[Array[Int]]] = Some([[I@1dd38904)

scala> res3.foreach{a2 => a2.foreach{a => a.foreach{i => println(s" $i")}; println("\n")}}
 6
 10
 5


 6
 10
 5


 6
 10
 5

This only generates one row then repeats this same row throughout the matrix. My solution to this would be to use scalacheck to generate order^2 elements then group them into your 2D array structure after the fact. Here is a copy/paste from my Scala REPL where I did this:

scala> :paste
// Entering paste mode (ctrl-D to finish)

    def matrix2[T: ClassTag](g: Gen[T])(order: Int): Gen[Array[Array[T]]] = 
      Gen.listOfN(order*order, g).map { squareList =>
        squareList.toArray.grouped(order).toArray
      }


// Exiting paste mode, now interpreting.

matrix2: [T](g: org.scalacheck.Gen[T])(order: Int)(implicit evidence$1: scala.reflect.ClassTag[T])org.scalacheck.Gen[Array[Array[T]]]

scala> matrix2(Gen.choose(1,10))(3)
res5: org.scalacheck.Gen[Array[Array[Int]]] = org.scalacheck.Gen$$anon$6@7519bc73

scala> res5.sample.foreach{a2 => a2.foreach{a => a.foreach{i => println(s" $i")}; println("\n")}}
 1
 9
 9


 7
 6
 10


 10
 2
 6

#3 The order will not change for this generator as you are using listOfN method which fixes the size of the collection. The example in the user guide only resizes because it uses the Gen.sized generator.

Community
  • 1
  • 1
Asa
  • 1,416
  • 9
  • 26