All Downloads are FREE. Search and download functionalities are using the official Maven repository.

spire.random.Random.scala Maven / Gradle / Ivy

package spire.random

import scala.collection.mutable.{ArrayBuffer, Builder}
import scala.collection.SeqLike
import scala.collection.generic.CanBuildFrom
import scala.annotation.tailrec
import scala.{specialized => spec}
import scala.reflect.ClassTag

sealed trait Op[+A] {

  def flatMap[B](f: A => Op[B]): Op[B] =
    this match {
      case FlatMap(a, g) => FlatMap(a, (x: Any) => g(x).flatMap(f))
      case o => FlatMap(o, f)
    }

  def map[B](f: A => B): Op[B] =
    flatMap(a => Const(f(a)))

  @tailrec
  final def resume(gen: Generator): Either[() => Op[A], A] =
    this match {
      case Const(a) =>
        Right(a)
      case More(k) =>
        Left(k)
      case Next(f) =>
        Right(f(gen))
      case FlatMap(a, f) =>
        a match {
          case Const(x) => f(x).resume(gen)
          case More(k) => Left(() => FlatMap(k(), f))
          case Next(g) => f(g(gen)).resume(gen)
          case FlatMap(b, g) => (FlatMap(b, (x: Any) => FlatMap(g(x), f)): Op[A]).resume(gen)
        }
    }

  def run(gen: Generator): A = {
    def loop(e: Either[() => Op[A], A]): A = e match {
      case Right(a) => a
      case Left(k) => loop(k().resume(gen))
    }
    loop(resume(gen))
  }
}

case class Const[+A](a: A) extends Op[A]
case class More[+A](k: () => Op[A]) extends Op[A]
case class Next[+A](f: Generator => A) extends Op[A]
case class FlatMap[A, +B](sub: Op[A], k: A => Op[B]) extends Op[B]

object Random extends RandomCompanion[rng.Cmwc5] {
  def initGenerator(): spire.random.rng.Cmwc5 = rng.Cmwc5.fromTime()

  def spawn[B](op: Op[B]): RandomCmwc5[B] = new RandomCmwc5(op)
}

trait RandomCompanion[G <: Generator] { self =>
  type R[X] = Random[X, G]

  def initGenerator(): G //IO

  def generatorFromSeed(seed: Seed): G = {
    val gen = initGenerator()
    gen.setSeedBytes(seed.bytes)
    gen
  }

  def spawn[B](op: Op[B]): R[B]

  def next[B](f: Generator => B): R[B] = spawn(Next(f))

  def fromDist[B](dist: Dist[B]): R[B] = spawn(Next(g => dist(g)))

  def constant[B](b: B): R[B] = spawn(Const(b))

  def unit: R[Unit] = constant(Unit)
  def boolean: R[Boolean] = next(_.nextBoolean)
  def byte: R[Byte] = next(_.nextInt.toByte)
  def short: R[Short] = next(_.nextInt.toShort)
  def char: R[Char] = next(_.nextInt.toChar)

  def int: R[Int] = next(_.nextInt)
  def int(n: Int): R[Int] = next(_.nextInt(n))
  def int(n1: Int, n2: Int): R[Int] = next(_.nextInt(n1, n2))

  def float: R[Float] = next(_.nextFloat)
  def long: R[Long] = next(_.nextLong)
  def double: R[Double] = next(_.nextDouble)

  def string(size: Size): R[String] =
    size.random(this).flatMap(stringOfSize)

  def stringOfSize(n: Int): Random[String, G] =
    char.foldLeftOfSize(n)(new StringBuilder) { (sb, c) => sb.append(c); sb }.map(_.toString)

  implicit class RandomOps[A](lhs: R[A]) {
    def collection[CC[_]](size: Size)(implicit cbf: CanBuildFrom[CC[A], A, CC[A]]): Random[CC[A], G] =
      size.random(self).flatMap(collectionOfSize(_))

    def collectionOfSize[CC[_]](n: Int)(implicit cbf: CanBuildFrom[CC[A], A, CC[A]]): Random[CC[A], G] =
      foldLeftOfSize(n)(cbf()) { (b, a) => b += a; b }.map(_.result)

    def foldLeftOfSize[B](n: Int)(init: => B)(f: (B, A) => B): Random[B, G] = {
      def loop(n: Int, ma: Op[A]): Op[B] =
        if (n <= 0) Const(init)
        else More(() => loop(n - 1, ma)).flatMap(b => ma.map(a => f(b, a)))
      spawn(loop(n, More(() => lhs.op)))
    }

    def unfold[B](init: B)(f: (B, A) => Option[B]): Random[B, G] = {
      def loop(mb: Op[B], ma: Op[A]): Op[B] =
        mb.flatMap(b => ma.flatMap(a => f(b, a) match {
          case Some(b2) => More(() => loop(Const(b2), ma))
          case None => Const(b)
        }))
      spawn(loop(Const(init), More(() => lhs.op)))
    }
  }

  def tuple2[A, B](r1: R[A], r2: R[B]): R[(A, B)] =
    r1 and r2
  def tuple3[A, B, C](r1: R[A], r2: R[B], r3: R[C]): R[(A, B, C)] =
    for { a <- r1; b <- r2; c <- r3 } yield (a, b, c)
  def tuple4[A, B, C, D](r1: R[A], r2: R[B], r3: R[C], r4: R[D]): R[(A, B, C, D)] =
    for { a <- r1; b <- r2; c <- r3; d <- r4 } yield (a, b, c, d)
}

abstract class Random[+A, G <: Generator](val op: Op[A]) { self =>

  def companion: RandomCompanion[G]

  def map[B](f: A => B): Random[B, G] =
    companion.spawn(op.map(f))

  def flatMap[B](f: A => Random[B, G]): Random[B, G] =
    companion.spawn(op.flatMap(f(_).op))

  def run(): A =
    op.run(companion.initGenerator) //IO

  def run(seed: Seed): A = { //IO
    val gen = companion.initGenerator()
    gen.setSeedBytes(seed.bytes)
    op.run(gen)
  }

  def some: Random[Some[A], G] = map(Some(_))
  def left: Random[Left[A, Nothing], G] = map(Left(_))
  def right: Random[Right[Nothing, A], G] = map(Right(_))

  def option: Random[Option[A], G] =
    companion.boolean.flatMap(b => if (b) some else companion.constant(None))

  def or[B](that: Random[B, G]): Random[Either[A, B], G] =
    companion.boolean.flatMap(b => if (b) left else that.right)

  def and[B](that: Random[B, G]): Random[(A, B), G] =
    for { a <- this; b <- that } yield (a, b)

  def recurse[B](body: => Random[B, G]): Random[B, G] =
    companion.spawn(More(() => body.op))

  def list(size: Size): Random[List[A], G] =
    size.random(companion).flatMap(listOfSize)

  def listOfSize(n: Int): Random[List[A], G] =
    companion.RandomOps(this).foldLeftOfSize(n)(List.empty[A])((as, a) => a :: as)
}

class RandomCmwc5[+A](op: Op[A]) extends Random[A, rng.Cmwc5](op) {
  def companion: Random.type = Random
}

sealed trait Size {
  def random[G <: Generator](r: RandomCompanion[G]): Random[Int, G]
}

object Size {
  def apply(n: Int): Size = Exact(n)
  def upTo(n: Int): Size = Between(0, n)
  def between(n1: Int, n2: Int): Size = Between(n1, n2)

  case class Exact(n: Int) extends Size {
    def random[G <: Generator](r: RandomCompanion[G]): Random[Int, G] = r.spawn(Const(n))
  }

  case class Between(n1: Int, n2: Int) extends Size {
    def random[G <: Generator](r: RandomCompanion[G]): Random[Int, G] = r.int(n1, n2)
  }
}

class Seed private[spire] (private[spire] val bytes: Array[Byte])

object Seed {
  val zero = Seed(Array[Byte](0, 0, 0, 0))
  def apply(n: Int): Seed = new Seed(spire.util.Pack.intToBytes(n))
  def apply(n: Long): Seed = new Seed(spire.util.Pack.longToBytes(n))
  def apply(bytes: Array[Byte]): Seed = new Seed(bytes.clone)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy