/* __ *\ ** ________ ___ / / ___ Scala API ** ** / __/ __// _ | / / / _ | (c) 2006-2009, LAMP/EPFL ** ** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** ** /____/\___/_/ |_/____/_/ | | ** ** |/ ** \* */ // $Id: Range.scala 18478 2009-08-13 21:30:20Z stepancheg $ package scala import annotation.experimental import collection.immutable.Vector import collection.generic.VectorView import util.control.Exception.catching import util.Hashable /** <p> * <code>GenericRange</code> is a generified version of the * <code>Range</code> class which works with arbitrary types. * It must be supplied with an Integral implementation of the * range type. * * Factories for likely types include Range.BigInt, Range.Long, * and Range.BigDecimal. Range.Int exists for completeness, but * the Int-based scala.Range should be more performant. * </p><pre> * <b>val</b> r1 = new Range(0, 100, 1) * <b>val</b> veryBig = Math.MAX_INT.toLong + 1 * <b>val</b> r2 = Range.Long(veryBig, veryBig + 100, 1) * assert(r1 sameElements r2.map(_ - veryBig)) * </pre> * * @author Paul Phillips * @version 2.8 */ @experimental abstract class GenericRange[T] (val start: T, val end: T, val step: T, val isInclusive: Boolean = false) (implicit num: Integral[T]) extends VectorView[T, Vector[T]] with RangeToString[T] with Hashable { import num._ // todo? - we could lift the length restriction by implementing a range as a sequence of // subranges and limiting the subranges to MAX_INT. There's no other way around it because // the generics we inherit assume integer-based indexing (as well they should.) require(!(step equiv zero)) require(genericLength <= fromInt(Math.MAX_INT), "Implementation restricts ranges to Math.MAX_INT elements.") // By adjusting end based on isInclusive, we can treat all ranges as exclusive. private lazy val trueEnd: T = if (isInclusive) end + step else end protected def underlying = Vector.empty[T] /** Create a new range with the start and end values of this range and * a new <code>step</code>. */ def by(newStep: T): GenericRange[T] = copy(start, end, newStep) /** Create a copy of this range. */ def copy(start: T, end: T, step: T): GenericRange[T] /** Shift or multiply the entire range by some constant. */ def -(shift: T) = this + negate(shift) def +(shift: T) = copy(this.start + shift, this.end + shift, step) def *(mult: T) = copy(this.start * mult, this.end * mult, step * mult) override def foreach[U](f: T => U) { var i = start if (step > zero) { while (i < trueEnd) { f(i) i = i + step } } else { while (i > trueEnd) { f(i) i = i + step } } } lazy val genericLength: T = { def plen(s: T, e: T, stp: T) = if (e <= s) zero else ((e - s) / stp) if (step > zero) plen(start, trueEnd, step) else plen(trueEnd, start, -step) } lazy val length: Int = toInt(genericLength) // Since apply(Int) already exists, we are not allowed apply(T) since // they erase to the same thing. def apply(idx: Int): T = applyAt(fromInt(idx)) def applyAt(idx: T): T = { if (idx < zero || idx >= genericLength) throw new IndexOutOfBoundsException(idx.toString) start + (idx * step) } // The contains situation makes for some interesting code. // This attempts to check containerhood in a range-sensible way, but // falls back on super.contains if the cast ends up failing. override def contains(_x: Any): Boolean = { def doContains = { // checking for Int is important so for instance BigIntRange from // 1 to Googlefinity can see if 5 is in there without calling super. val x = _x match { case i: Int => fromInt(i) case _ => _x.asInstanceOf[T] } def matchesStep = (x - start) % step == zero def withinRange = if (step > zero) start <= x && x < trueEnd else start >= x && x > trueEnd withinRange && matchesStep } catching(classOf[ClassCastException]) opt doContains getOrElse super.contains(_x) } // Using trueEnd gives us Range(1, 10, 1).inclusive == Range(1, 11, 1) val hashValues = List(start, trueEnd, step) override def equals(other: Any) = other match { case x: GenericRange[_] => this equalHashValues x case _ => false } } private[scala] trait RangeToString[T] extends VectorView[T, Vector[T]] { // The default toString() tries to print every element and will exhaust memory // if the Range is unduly large. This interacts poorly with the REPL. override def toString() = { val MAX_PRINT = 512 // some arbitrary value val str = (this take MAX_PRINT).mkString(", ") if (length > MAX_PRINT) str.replaceAll("""\)$""", ", ...)") else str } } object GenericRange { class Inclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]) extends GenericRange(start, end, step, true) { def exclusive: Exclusive[T] = GenericRange(start, end, step) def copy(start: T, end: T, step: T): Inclusive[T] = GenericRange.inclusive(start, end, step) } class Exclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]) extends GenericRange(start, end, step, false) { def inclusive: Inclusive[T] = GenericRange.inclusive(start, end, step) def copy(start: T, end: T, step: T): Exclusive[T] = GenericRange(start, end, step) } def apply[T](start: T, end: T, step: T)(implicit num: Integral[T]): Exclusive[T] = new Exclusive(start, end, step) def inclusive[T](start: T, end: T, step: T)(implicit num: Integral[T]): Inclusive[T] = new Inclusive(start, end, step) } /** <p> * The <code>Range</code> class represents integer values in range * <code>[start;end)</code> with non-zero step value <code>step</code>. * Sort of acts like a sequence also (supports length and contains). * For example: * </p><pre> * <b>val</b> r1 = 0 until 10 * <b>val</b> r2 = r1.start until r1.end by r1.step + 1 * println(r2.length) // = 5 * </pre> * * @author Martin Odersky * @version 2.8 * @since 2.5 */ class Range(val start: Int, val end: Int, val step: Int) extends VectorView[Int, Vector[Int]] with RangeToString[Int] { require(step != 0) protected def underlying = Vector.empty[Int] /** Create a new range with the start and end values of this range and * a new <code>step</code>. */ def by(step: Int): Range = new Range(start, end, step) final override def foreach[U](f: Int => U) { var i = start if (step > 0) { while (i < end) { f(i) i += step } } else { while (i > end) { f(i) i += step } } } lazy val length: Int = { def plen(start: Int, end: Int, step: Int) = if (end <= start) 0 else (end - start - 1) / step + 1 if (step > 0) plen(start, end, step) else plen(end, start, -step) } @inline final def apply(idx: Int): Int = { if (idx < 0 || idx >= length) throw new IndexOutOfBoundsException(idx.toString) start + idx * step } def contains(x: Int): Boolean = if (step > 0) start <= x && x < end else start >= x && x > end def inclusive = Range.inclusive(start, end, step) // XXX right now (1 to 10).toList == (1 to 10) but their hashCodes are unequal. override def equals(other: Any) = other match { case x: Range => start == x.start && end == x.end && step == x.step case _ => super.equals(other) } override def hashCode = start + end + step } object Range { @deprecated("use Range.inclusive instead") final class Inclusive(start: Int, end0: Int, step: Int) extends Range(start, if (step > 0) end0 + 1 else end0 - 1, step) { self => override def by(step: Int): Range = new Inclusive(start, end0, step) } // The standard / Int-specific Range. def apply(start: Int, end: Int, step: Int) = new Range(start, end, step) def inclusive(start: Int, end: Int, step: Int): Range = new Range.Inclusive(start, end, step) // BigInt and Long are straightforward generic ranges. object BigInt { def apply(start: BigInt, end: BigInt, step: BigInt) = GenericRange(start, end, step) def inclusive(start: BigInt, end: BigInt, step: BigInt) = GenericRange.inclusive(start, end, step) } object Long { def apply(start: Long, end: Long, step: Long) = GenericRange(start, end, step) def inclusive(start: Long, end: Long, step: Long) = GenericRange.inclusive(start, end, step) } // BigDecimal uses an alternative implementation of Numeric in which // it pretends to be Integral[T] instead of Fractional[T]. See Numeric for // details. The intention is for it to throw an exception anytime // imprecision or surprises might result from anything, although this may // not yet be fully implemented. object BigDecimal { implicit val bigDecAsIntegral = scala.Numeric.BigDecimalAsIfIntegral def apply(start: BigDecimal, end: BigDecimal, step: BigDecimal) = GenericRange(start, end, step) def inclusive(start: BigDecimal, end: BigDecimal, step: BigDecimal) = GenericRange.inclusive(start, end, step) } // Double re-uses BigDecimal's range. object Double { def apply(start: Double, end: Double, step: Double) = scala.BigDecimal(start) until end by step def inclusive(start: Double, end: Double, step: Double) = scala.BigDecimal(start) to end by step } // As there is no appealing default step size for not-really-integral ranges, // we offer a partially constructed object. class Partial[T, U](f: T => U) { def by(x: T): U = f(x) } // Illustrating genericity with Int Range, which should have the same behavior // as the original Range class. However we leave the original Range // indefinitely, for performance and because the compiler seems to bootstrap // off it and won't do so with our parameterized version without modifications. object Int { def apply(start: Int, end: Int, step: Int) = GenericRange(start, end, step) def inclusive(start: Int, end: Int, step: Int) = GenericRange.inclusive(start, end, step) } }