Skip to content

Commit c4ef98e

Browse files
authored
Merge pull request #345 from xuwei-k/max-min-Option
add minOption, maxOption, minByOption and maxByOption
2 parents 47668ef + f3a2c0b commit c4ef98e

File tree

3 files changed

+170
-1
lines changed

3 files changed

+170
-1
lines changed

compat/src/main/scala-2.11_2.12/scala/collection/compat/PackageShared.scala

+28
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,34 @@ class IteratorExtensionMethods[A](private val self: c.Iterator[A]) extends AnyVa
256256

257257
class TraversableOnceExtensionMethods[A](private val self: c.TraversableOnce[A]) extends AnyVal {
258258
def iterator: Iterator[A] = self.toIterator
259+
260+
def minOption[B >: A](implicit ord: Ordering[B]): Option[A] = {
261+
if (self.isEmpty)
262+
None
263+
else
264+
Some(self.min(ord))
265+
}
266+
267+
def maxOption[B >: A](implicit ord: Ordering[B]): Option[A] = {
268+
if (self.isEmpty)
269+
None
270+
else
271+
Some(self.max(ord))
272+
}
273+
274+
def minByOption[B](f: A => B)(implicit cmp: Ordering[B]): Option[A] = {
275+
if (self.isEmpty)
276+
None
277+
else
278+
Some(self.minBy(f)(cmp))
279+
}
280+
281+
def maxByOption[B](f: A => B)(implicit cmp: Ordering[B]): Option[A] = {
282+
if (self.isEmpty)
283+
None
284+
else
285+
Some(self.maxBy(f)(cmp))
286+
}
259287
}
260288

261289
class TraversableExtensionMethods[A](private val self: c.Traversable[A]) extends AnyVal {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Scala (https://www.scala-lang.org)
3+
*
4+
* Copyright EPFL and Lightbend, Inc.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (http://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package test.scala.collection
14+
15+
import org.junit.Assert._
16+
import org.junit.Test
17+
18+
import scala.util.Random
19+
import scala.reflect.ClassTag
20+
import scala.util.control.NonFatal
21+
22+
import scala.collection.compat._
23+
24+
// whole file copied/adapted from same-named file in scala/scala repo
25+
26+
/* Test for scala/bug#7614 */
27+
class MinByMaxByTest {
28+
val list = List.fill(1000)(Random.nextInt(10000) - 5000)
29+
30+
// next two methods copied from AssertUtil in scala/scala repo
31+
32+
/** Check that throwable T (or a subclass) was thrown during evaluation of `body`,
33+
* and that its message satisfies the `checkMessage` predicate.
34+
* Any other exception is propagated.
35+
*/
36+
def assertThrows[T <: Throwable: ClassTag](body: => Any,
37+
checkMessage: String => Boolean = s => true): Unit = {
38+
assertThrown[T](t => checkMessage(t.getMessage))(body)
39+
}
40+
41+
def assertThrown[T <: Throwable: ClassTag](checker: T => Boolean)(body: => Any): Unit =
42+
try {
43+
body
44+
fail("Expression did not throw!")
45+
} catch {
46+
case e: T if checker(e) => ()
47+
case failed: T =>
48+
val ae = new AssertionError(s"Exception failed check: $failed")
49+
ae.addSuppressed(failed)
50+
throw ae
51+
case NonFatal(other) =>
52+
val ae = new AssertionError(
53+
s"Wrong exception: expected ${implicitly[ClassTag[T]]} but was ${other.getClass.getName}")
54+
ae.addSuppressed(other)
55+
throw ae
56+
}
57+
58+
// Basic emptiness check
59+
@Test
60+
def checkEmpty(): Unit = {
61+
assertThrows[UnsupportedOperationException](List[Int]().maxBy(_ * 3))
62+
assertThrows[UnsupportedOperationException](List[Int]().minBy(_ * 3))
63+
}
64+
65+
// Basic definition of minBy/maxBy.
66+
@Test
67+
def testCorrectness() = {
68+
def f(x: Int) = -1 * x
69+
val max = list.maxBy(f)
70+
assertTrue("f(list.maxBy(f)) should ≥ f(x) where x is any element of list.",
71+
list.forall(f(_) <= f(max)))
72+
val min = list.minBy(f)
73+
assertTrue("f(list.minBy(f)) should ≤ f(x) where x is any element of list.",
74+
list.forall(f(_) >= f(min)))
75+
}
76+
77+
// Ensure that it always returns the first match if more than one element have the same largest/smallest f(x).
78+
// Note that this behavior is not explicitly stated before.
79+
// To make it compatible with the previous implementation, I add this behavior to docs.
80+
@Test
81+
def testReturnTheFirstMatch() = {
82+
val d = List(1, 2, 3, 4, 5, 6, 7, 8)
83+
def f(x: Int) = x % 3;
84+
assert(
85+
d.maxBy(f) == 2,
86+
"If multiple elements evaluated to the largest value, maxBy should return the first one.")
87+
assert(
88+
d.minBy(f) == 3,
89+
"If multiple elements evaluated to the largest value, minBy should return the first one.")
90+
}
91+
92+
// Make sure it evaluates f no more than list.length times.
93+
@Test
94+
def testOnlyEvaluateOnce() = {
95+
var evaluatedCountOfMaxBy = 0
96+
97+
val max = list.maxBy(x => {
98+
evaluatedCountOfMaxBy += 1
99+
x * 10
100+
})
101+
assert(
102+
evaluatedCountOfMaxBy == list.length,
103+
s"maxBy: should evaluate f only ${list.length} times, but it evaluated $evaluatedCountOfMaxBy times.")
104+
105+
var evaluatedCountOfMinBy = 0
106+
107+
val min = list.minBy(x => {
108+
evaluatedCountOfMinBy += 1
109+
x * 10
110+
})
111+
assert(
112+
evaluatedCountOfMinBy == list.length,
113+
s"minBy: should evaluate f only ${list.length} times, but it evaluated $evaluatedCountOfMinBy times.")
114+
}
115+
116+
@Test
117+
def checkEmptyOption(): Unit = {
118+
assert(Seq.empty[Int].maxOption == None, "maxOption on a Empty Iterable is None")
119+
assert(Seq.empty[Int].minOption == None, "minOption on a Empty Iterable is None")
120+
assert(Seq.empty[Int].maxByOption(identity) == None, "maxByOption on a Empty Iterable is None")
121+
assert(Seq.empty[Int].minByOption(identity) == None, "minByOption on a Empty Iterable is None")
122+
}
123+
124+
@Test
125+
def checkNonEmptyOption(): Unit = {
126+
assert(Seq(1).maxOption == Some(1), "maxOption on a Non Empty Iterable has value")
127+
assert(Seq(1).minOption == Some(1), "minOption on a Non Empty Iterable has value")
128+
assert(Seq(1).maxByOption(identity) == Some(1), "maxByOption on a Non Empty Iterable has value")
129+
assert(Seq(1).minByOption(identity) == Some(1), "minByOption on a Non Empty Iterable has value")
130+
}
131+
132+
/* commenting this out since behavior varies from Scala version to Scala version
133+
@Test
134+
def testMinMaxCorrectness(): Unit = {
135+
val seq = Seq(5.0, 3.0, Double.NaN, 4.0)
136+
assert(seq.min.isNaN)
137+
assert(seq.max.isNaN)
138+
}
139+
*/
140+
141+
}

compat/src/test/scala/test/scala/collection/generic/SortedTest.scala renamed to compat/src/test/scala/test/scala/collection/SortedTest.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* additional information regarding copyright ownership.
1111
*/
1212

13-
package test.scala.collection.generic
13+
package test.scala.collection
1414

1515
import org.junit.Assert._
1616
import org.junit.Test

0 commit comments

Comments
 (0)