4

I have some generators like this:

val fooRepr = oneOf(a, b, c, d, e)
val foo = for (s <- choose(1, 5); c <- listOfN(s, fooRepr)) yield c.mkString("$")

This leads to duplicates ... I might get two a's, etc. What I really want is to generate random permutation with exactly 0 or 1 or each of a, b, c, d, or e (with at least one of something), in any order.

I was thinking there must be an easy way, but I'm struggling to even find a hard way. :)

Edited: Ok, this seems to work:

val foo = for (s <- choose(1, 5);
               c <- permute(s, a, b, c, d, e)) yield c.mkString("$")

def permute[T](n: Int, gs: Gen[T]*): Gen[Seq[T]] = {
  val perm = Random.shuffle(gs.toList)
  for {
    is <- pick(n, 1 until gs.size)
    xs <- sequence[List,T](is.toList.map(perm(_)))
  } yield xs
}

...borrowing heavily from Gen.pick.

Thanks for your help, -Eric

4

2 回答 2

3

You're not describing a permutation, but the power set (minus the empty set)Edit: you're describing a combination of a power set and a permutation. The power set of an indexed set N is isomorphic to 2^N, so we simply (in Scala alone; maybe you want to alter this for use with ScalaCheck):

def powerSet[X](xs: List[X]) = {
  val xis = xs.zipWithIndex
  (for (j <- 1 until (1<<xs.length)) yield {
    for ((x,i) <- xis if ((j & (1<<i)) != 0)) yield x
  }).toList
}

to generate all possible subsets given a set. Of course, explicit generation of power sets is unwise if they original set contains more than a handful of elements. If you don't want to generate all of them, just pass in a random number from 1 until (1<<(xs.length-1)) and run the inner loop. (Switch to Long if there are 33-64 elements, and to BitSet if there are more yet.) You can then permute the result to switch the order around if you wish.


Edit: there's another way to do this if you can generate permutations easily and you can add a dummy argument: make your list one longer, with a Stop token. Then permute and .takeWhile(_ != Stop). Ta-da! Permutations of arbitrary length. (Filter out the zero-length answer if need be.)

于 2011-02-24T17:02:57.717 回答
3

Rex, thanks for clarifying exactly what I'm trying to do, and that's useful code, but perhaps not so nice with scalacheck, particularly if the generators in question are quite complex. In my particular case the generators a, b, c, etc. are generating huge strings.

Anyhow, there was a bug in my solution above; what worked for me is below. I put a tiny project demonstrating how to do this at github

The guts of it is below. If there's a better way, I'd love to know it...

package powerset

import org.scalacheck._
import org.scalacheck.Gen._
import org.scalacheck.Gen
import scala.util.Random

object PowersetPermutations extends Properties("PowersetPermutations") {

  def a: Gen[String] = value("a")

  def b: Gen[String] = value("b")

  def c: Gen[String] = value("c")

  def d: Gen[String] = value("d")

  def e: Gen[String] = value("e")

  val foo = for (s <- choose(1, 5);
                 c <- permute(s, a, b, c, d, e)) yield c.mkString

  def permute[T](n: Int, gs: Gen[T]*): Gen[Seq[T]] = {
    val perm = Random.shuffle(gs.toList)
    for {
      is <- pick(n, 0 until gs.size)
      xs <- sequence[List, T](is.toList.map(perm(_)))
    } yield xs
  }

  implicit def arbString: Arbitrary[String] = Arbitrary(foo)

  property("powerset") = Prop.forAll {
    a: String => println(a); true
  }
}

Thanks, Eric

于 2011-02-24T22:20:44.870 回答