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

hedgehog.core.Shrink.scala Maven / Gradle / Ivy

There is a newer version: 0.11.0
Show newest version
package hedgehog.core

object Shrink extends XCompat {

  /**
   * Shrink an integral number by edging towards a destination.
   *
   * {{{
   * scala> towards(0, 100)
   * List(0, 50, 75, 88, 94, 97, 99)
   *
   * scala> towards(500, 1000)
   * List(500, 750, 875, 938, 969, 985, 993, 997, 999)
   *
   * scala> towards(-50,  -26)
   * List(-50, -38, -32, -29, -27)
   * }}}
   *
   * ''Note we always try the destination first, as that is the optimal shrink.''
   */
  def towards[A](destination: A, x: A)(implicit I: Integral[A]): List[A] =
    if (destination == x) {
      Nil
    } else {
      // Halve the operands before subtracting them so they don't overflow.
      // Consider `min` and `max` for a fixed sized type like 'Int'.
      val diff = I.minus(I.quot(x, I.fromInt(2)), I.quot(destination, I.fromInt(2)))
      consNub(destination, halves(diff).map(I.minus(x, _)))
    }

  /**
   * Shrink a floating-point number by edging towards a destination.
   *
   * {{{
   * scala> towards(0.0, 100)
   * List(0.0, 50.0, 75.0, 87.5, 93.75, 96.875, 98.4375...)
   *
   * scala> towards(1.0, 0.5)
   * List(1.0, 0.75, 0.625, 0.5625, 0.53125, 0.515625, 0.5078125...)
   * }}}
   *
   * ''Note we always try the destination first, as that is the optimal shrink.''
   */
  def towardsFloat[A](destination: Double, x: Double): List[Double] =
    if (destination == x) {
      Nil
    } else {
      val diff = x - destination
      StreamOrLazyList
        .iterate(diff)(_ / 2)
        .map(x - _)
        .takeWhile(y => y != x && !y.isNaN && !y.isInfinite)
        // In _extremely_ rare cases it's possible to generate a value outside of our range if the diff is very very small
        .filter(y => if (destination < x) destination <= y && y <= x else x <= y && y <= destination)
        .toList
    }

  /**
   * Shrink a list by edging towards the empty list.
   *
   * {{{
   * scala> list(List(1, 2, 3))
   * List(List(), List(2, 3), List(1, 3), List(1, 2))
   *
   * >>> list("abcd".toList)
   * List("", "cd", "ab", "bcd", "acd", "abd", "abc")
   * }}}
   *
   * ''Note we always try the empty list first, as that is the optimal shrink.''
   */
  def list[A](xs: List[A]): List[List[A]] =
    halves(xs.length)
      // FIX: predef foldMap
      .foldLeft(List.empty[List[A]])((lla, k) => lla ++ removes(k, xs))

  /**
   * Produce all permutations of removing 'k' elements from a list.
   *
   * {{{
   * scala> removes(2, "abcdef".toList)
   * List("cdef", "abef", "abcd")
   * }}}
   */
  def removes[A](k0: Int, xs0: List[A]): List[List[A]] = {
    def loop(k: Int, n: Int, xs: List[A]): List[List[A]] = {
      val (hd, tl) = xs.splitAt(k)
      if (k > n)
        Nil
      else if (tl.isEmpty)
        List(Nil)
      else
        tl :: loop(k, n - k, tl).map(hd ++ _)
    }
    loop(k0, xs0.length, xs0)
  }

  /**
   * Produce a list containing the progressive halving of an integral.
   *
   * {{{
   * scala> halves(15)
   * List(15, 7, 3, 1)
   *
   * scala> halves(100)
   * List(100, 50, 25, 12, 6, 3, 1)
   *
   * scala> halves(-26)
   * List(-26, -13, -6, -3, -1)
   * }}}
   */
  def halves[A](a: A)(implicit I: Integral[A]): List[A] =
    StreamOrLazyList.iterate(a)(I.quot(_, I.fromInt(2))).takeWhile(_ != 0).toList

  /**
   * Cons an element on to the front of a list unless it is already there.
   */
  def consNub[A](x: A, ys0: List[A]): List[A] =
     ys0 match {
       case Nil =>
         x :: Nil
       case y :: ys =>
         if (x == y) y :: ys else x :: y :: ys
     }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy