Skip to content

Commit

Permalink
Add size comparison methods to IterableOps
Browse files Browse the repository at this point in the history
Add IterableOps.sizeCompare(Int), .sizeCompare(Iterable[_]), and
.sizeIs
  • Loading branch information
NthPortal committed Jul 24, 2018
1 parent 4f0908f commit 68161af
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 26 deletions.
8 changes: 7 additions & 1 deletion src/library/scala/collection/IndexedSeq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,16 @@ trait IndexedSeqOps[+A, +CC[_], +C] extends Any with SeqOps[A, CC, C] { self =>

override def last: A = apply(length - 1)

override def lengthCompare(len: Int): Int = Integer.compare(length, len)
override final def lengthCompare(len: Int): Int = Integer.compare(length, len)

final override def knownSize: Int = length

override final def sizeCompare(that: Iterable[_]): Int = {
val res = that.sizeCompare(length)
// can't just invert the result, because `-Int.MinValue == Int.MinValue`
if (res == Int.MinValue) 1 else -res
}

override def search[B >: A](elem: B)(implicit ord: Ordering[B]): SearchResult =
binarySearch(elem, 0, length)(ord)

Expand Down
102 changes: 102 additions & 0 deletions src/library/scala/collection/Iterable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,88 @@ trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with Iterable
/** A view over the elements of this collection. */
def view: View[A] = View.fromIteratorProvider(() => iterator)

/** Compares the size of this $coll to a test value.
*
* @param otherSize the test value that gets compared with the size.
* @return A value `x` where
* {{{
* x < 0 if this.size < otherSize
* x == 0 if this.size == otherSize
* x > 0 if this.size > otherSize
* }}}
* The method as implemented here does not call `size` directly; its running time
* is `O(size min _size)` instead of `O(size)`. The method should be overwritten
* if computing `size` is cheap.
*/
def sizeCompare(otherSize: Int): Int = {
if (otherSize < 0) 1
else {
val known = knownSize
if (known >= 0) Integer.compare(known, otherSize)
else {
var i = 0
val it = iterator
while (it.hasNext) {
if (i == otherSize) return if (it.hasNext) 1 else 0
it.next()
i += 1
}
i - otherSize
}
}
}

/** Returns a value class containing operations for comparing the size of this $coll to a test value.
*
* These operations are implemented in terms of [[sizeCompare(Int) `sizeCompare(Int)`]], and
* allow the following more readable usages:
*
* {{{
* this.sizeIs < size // this.sizeCompare(size) < 0
* this.sizeIs <= size // this.sizeCompare(size) <= 0
* this.sizeIs == size // this.sizeCompare(size) == 0
* this.sizeIs != size // this.sizeCompare(size) != 0
* this.sizeIs >= size // this.sizeCompare(size) >= 0
* this.sizeIs > size // this.sizeCompare(size) > 0
* }}}
*/
@inline final def sizeIs: IterableOps.SizeCompareOps = new IterableOps.SizeCompareOps(this)

/** Compares the size of this $coll to the size of another `Iterable`.
*
* @param that the `Iterable` whose size is compared with this $coll's size.
* {{{
* x < 0 if this.size < that.size
* x == 0 if this.size == that.size
* x > 0 if this.size > that.size
* }}}
* The method as implemented here does not call `size` directly; its running time
* is `O(this.size min that.size)` instead of `O(this.size + that.size)`.
* The method should be overwritten if computing `size` is cheap.
*/
def sizeCompare(that: Iterable[_]): Int = {
val thatKnownSize = that.knownSize

if (thatKnownSize >= 0) this sizeCompare thatKnownSize
else {
val thisKnownSize = this.knownSize

if (thisKnownSize >= 0) {
val res = that sizeCompare thisKnownSize
// can't just invert the result, because `-Int.MinValue == Int.MinValue`
if (res == Int.MinValue) 1 else -res
} else {
val thisIt = this.iterator
val thatIt = that.iterator
while (thisIt.hasNext && thatIt.hasNext) {
thisIt.next()
thatIt.next()
}
java.lang.Boolean.compare(thisIt.hasNext, thatIt.hasNext)
}
}
}

/** A view over a slice of the elements of this collection. */
@deprecated("Use .view.slice(from, until) instead of .view(from, until)", "2.13.0")
@`inline` final def view(from: Int, until: Int): View[A] = view.slice(from, until)
Expand Down Expand Up @@ -690,6 +772,26 @@ trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with Iterable

object IterableOps {

/** Operations for comparing the size of a collection to a test value.
*
* These operations are implemented in terms of
* [[scala.collection.IterableOps.sizeCompare(Int) `sizeCompare(Int)`]].
*/
final class SizeCompareOps private[collection](val it: IterableOps[_, AnyConstr, _]) extends AnyVal {
/** Tests if the size of the collection is less than some value. */
@inline def <(size: Int): Boolean = it.sizeCompare(size) < 0
/** Tests if the size of the collection is less than or equal to some value. */
@inline def <=(size: Int): Boolean = it.sizeCompare(size) <= 0
/** Tests if the size of the collection is equal to some value. */
@inline def ==(size: Int): Boolean = it.sizeCompare(size) == 0
/** Tests if the size of the collection is not equal to some value. */
@inline def !=(size: Int): Boolean = it.sizeCompare(size) != 0
/** Tests if the size of the collection is greater than or equal to some value. */
@inline def >=(size: Int): Boolean = it.sizeCompare(size) >= 0
/** Tests if the size of the collection is greater than some value. */
@inline def >(size: Int): Boolean = it.sizeCompare(size) > 0
}

/** A trait that contains just the `map`, `flatMap`, `foreach` and `withFilter` methods
* of trait `Iterable`.
*
Expand Down
22 changes: 4 additions & 18 deletions src/library/scala/collection/Seq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,8 @@ trait SeqOps[+A, +CC[_], +C] extends Any
*/
def indices: Range = Range(0, length)

override final def sizeCompare(_size: Int): Int = lengthCompare(_size)

/** Compares the length of this $coll to a test value.
*
* @param len the test value that gets compared with the length.
Expand All @@ -674,23 +676,7 @@ trait SeqOps[+A, +CC[_], +C] extends Any
* is `O(length min len)` instead of `O(length)`. The method should be overwritten
* if computing `length` is cheap.
*/
def lengthCompare(len: Int): Int = {
if (len < 0) 1
else {
val known = knownSize
if (known >= 0) Integer.compare(known, len)
else {
var i = 0
val it = iterator
while (it.hasNext) {
if (i == len) return if (it.hasNext) 1 else 0
it.next()
i += 1
}
i - len
}
}
}
def lengthCompare(len: Int): Int = super.sizeCompare(len)

/** Returns a value class containing operations for comparing the length of this $coll to a test value.
*
Expand All @@ -706,7 +692,7 @@ trait SeqOps[+A, +CC[_], +C] extends Any
* this.lengthIs > len // this.lengthCompare(len) > 0
* }}}
*/
@inline final def lengthIs: SeqOps.LengthCompareOps = new SeqOps.LengthCompareOps(this)
@inline final def lengthIs: IterableOps.SizeCompareOps = new IterableOps.SizeCompareOps(this)

override def isEmpty: Boolean = lengthCompare(0) == 0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import scala.util.Random
@Measurement(iterations = 10)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
class LengthCompareOpsBenchmark {
class SizeCompareOpsBenchmark {
@Param(Array("0", "1", "10", "100", "1000"))
var size: Int = _

@Param(Array("1", "100", "10000"))
var len: Int = _
var cmpTo: Int = _

var values: List[Int] = _

Expand All @@ -27,11 +27,11 @@ class LengthCompareOpsBenchmark {
values = List.fill(size)(Random.nextInt())
}

@Benchmark def lengthCompareUgly: Any = {
values.lengthCompare(len) == 0
@Benchmark def sizeCompareUgly: Any = {
values.sizeCompare(cmpTo) == 0
}

@Benchmark def lengthComparePretty: Any = {
values.lengthIs == len
@Benchmark def sizeComparePretty: Any = {
values.sizeIs == cmpTo
}
}
32 changes: 31 additions & 1 deletion test/junit/scala/collection/IterableTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package scala.collection
import org.junit.{Assert, Test}
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import scala.collection.immutable.{ArraySeq, List, Range}

import scala.collection.immutable.{ArraySeq, List, Range, Vector}
import scala.language.higherKinds
import scala.tools.testing.AssertUtil._

@RunWith(classOf[JUnit4])
Expand Down Expand Up @@ -58,6 +60,34 @@ class IterableTest {
Assert.assertEquals(expected, occurrences(xs))
}

@Test
def sizeCompareInt(): Unit = {
val seq = Seq(1, 2, 3)
assert(seq.sizeCompare(2) > 0)
assert(seq.sizeCompare(3) == 0)
assert(seq.sizeCompare(4) < 0)
}

@Test
def sizeCompareIterable(): Unit = {
def check[I1[X] <: Iterable[X], I2[X] <: Iterable[X]]
(f1: IterableFactory[I1], f2: IterableFactory[I2]): Unit = {
val it = f1(1, 2, 3)
assert(it.sizeCompare(f2(1, 2)) > 0)
assert(it.sizeCompare(f2(1, 2, 3)) == 0)
assert(it.sizeCompare(f2(1, 2, 3, 4)) < 0)
}

// factories for `Seq`s with known and unknown size
val known: IterableFactory[IndexedSeq] = Vector
val unknown: IterableFactory[LinearSeq] = List

check(known, known)
check(known, unknown)
check(unknown, known)
check(unknown, unknown)
}

@Test def copyToArray(): Unit = {
def check(a: Array[Int], start: Int, end: Int) = {
var i = 0
Expand Down

0 comments on commit 68161af

Please sign in to comment.