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

doodle.random.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015 Creative Scala
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package doodle

import cats.Comonad
import cats.free.Free

import scala.annotation.tailrec
import scala.util.Random as Rng

object random {
  type Random[A] = Free[RandomOp, A]

  sealed abstract class RandomOp[A]
  object RandomOp {
    final case class Always[A](get: A) extends RandomOp[A]
    final case class Discrete[A](elements: Seq[(A, Double)]) extends RandomOp[A]
    final case class Natural(upperLimit: Int) extends RandomOp[Int]
    case object RInt extends RandomOp[Int]
    case object RDouble extends RandomOp[Double]
    case object Normal extends RandomOp[Double]
  }

  implicit def randomInstances(implicit
      rng: Rng = scala.util.Random
  ): Comonad[RandomOp] =
    new Comonad[RandomOp] {
      import RandomOp.*

      @tailrec
      def pick[A](total: Double, weight: Double, events: Seq[(A, Double)]): A =
        events match {
          case (a, p) +: rest =>
            if total < weight && weight < (total + p) then a
            else pick(total + p, weight, rest)
          case _ =>
            throw new Exception("Could not sample---ran out of events!")
        }

      override def coflatMap[A, B](fa: RandomOp[A])(
          f: (RandomOp[A]) => B
      ): RandomOp[B] =
        Always(f(fa))
      override def extract[A](x: RandomOp[A]): A =
        x match {
          case Always(a) => a
          case Discrete(elts) =>
            val weight = rng.nextDouble()
            pick(0.0, weight, elts)
          case RInt       => rng.nextInt()
          case Natural(u) => rng.nextInt(u)
          case RDouble    => rng.nextDouble()
          case Normal     => rng.nextGaussian()
        }
      override def map[A, B](fa: RandomOp[A])(f: (A) => B): RandomOp[B] =
        Always(f(extract(fa)))
    }

  object Random {
    import RandomOp.*

    /** Create a `Random` that always generates the given value. */
    def always[A](in: A): Random[A] =
      Free.pure(in)

    /** Create a `Random` that generates an `Int` uniformly distributed across
      * the entire range.
      */
    def int: Random[Int] =
      Free.liftF[RandomOp, Int](RInt)

    /** Create a `Random` that generates an `Int` uniformly distributed in the
      * range greater than or equal to `lower` and less than `upper`.
      */
    def int(lower: Int, upper: Int): Random[Int] = {
      val high = (upper max lower)
      val low = (upper min lower)
      val range = Math.abs(high - low)
      natural(range).map { n =>
        n + low
      }
    }

    /** Create a `Random` that generates an `Int` uniformly distributed in the
      * range greater than or equal to zero and less than `upper`.
      */
    def natural(upperLimit: Int): Random[Int] =
      Free.liftF[RandomOp, Int](Natural(upperLimit))

    /** Create a `Random` that generates a `Double` uniformly distributed
      * between 0.0 and 1.0.
      */
    def double: Random[Double] =
      Free.liftF[RandomOp, Double](RDouble)

    /** Create a `Random` that generates one of the provided values with uniform
      * distribution.
      */
    def oneOf[A](elts: A*): Random[A] = {
      val length = elts.length
      Random.natural(length).map(idx => elts(idx))
    }

    def discrete[A](elts: (A, Double)*): Random[A] =
      Free.liftF[RandomOp, A](Discrete(elts))

    def normal: Random[Double] =
      Free.liftF[RandomOp, Double](Normal)

    def normal(mean: Double, stdDev: Double): Random[Double] =
      Random.normal.map(x => (stdDev * x) + mean)
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy