From 055c71e8d0a22dfa17c031ce75e1e04e5e0e55c5 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Mon, 30 Nov 2015 19:57:42 +0300 Subject: [PATCH] Provide minWith and maxWith to find maximum and minimum values according to the given comparator. #KT-9002 Fixed --- .../src/core/javautilCollections.kt | 1 + .../js/test/semantics/StdLibTestToJSTest.java | 2 +- libraries/stdlib/src/generated/_Arrays.kt | 234 ++++++++++++++++++ .../stdlib/src/generated/_Collections.kt | 28 +++ libraries/stdlib/src/generated/_Maps.kt | 14 ++ libraries/stdlib/src/generated/_Sequences.kt | 28 +++ libraries/stdlib/src/generated/_Strings.kt | 26 ++ libraries/stdlib/test/OrderingTest.kt | 2 + .../stdlib/test/collections/ArraysTest.kt | 23 ++ .../stdlib/test/collections/CollectionTest.kt | 15 ++ .../src/templates/Aggregates.kt | 61 +++++ 11 files changed, 433 insertions(+), 1 deletion(-) diff --git a/js/js.libraries/src/core/javautilCollections.kt b/js/js.libraries/src/core/javautilCollections.kt index a7a130832a2..cdcc68818c6 100644 --- a/js/js.libraries/src/core/javautilCollections.kt +++ b/js/js.libraries/src/core/javautilCollections.kt @@ -4,6 +4,7 @@ import java.lang.* import java.util.* public object Collections { + @Deprecated("Use collection.maxWith(comparator) instead.", ReplaceWith("col.maxWith(comp)")) public fun max(col: Collection, comp: Comparator): T = java.util.max(col, comp) @Deprecated("Use list.sort() instead.", ReplaceWith("list.sort()")) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/StdLibTestToJSTest.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/StdLibTestToJSTest.java index ff83fb8d3a7..c9277a771cc 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/StdLibTestToJSTest.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/StdLibTestToJSTest.java @@ -34,7 +34,7 @@ public class StdLibTestToJSTest extends StdLibQUnitTestSupport { "collections/ComparisonDSL.kt", "../../../js/js.libraries/test/core/assertTypeEquals.kt", "text/StringTest.kt", - // TODO review: somethings FAILED if run: + "OrderingTest.kt", "collections/SequenceTest.kt", "collections/IterableTests.kt", "collections/ArraysTest.kt", diff --git a/libraries/stdlib/src/generated/_Arrays.kt b/libraries/stdlib/src/generated/_Arrays.kt index 10dad439daa..74075e8f57c 100644 --- a/libraries/stdlib/src/generated/_Arrays.kt +++ b/libraries/stdlib/src/generated/_Arrays.kt @@ -8725,6 +8725,123 @@ public inline fun > ShortArray.maxBy(selector: (Short) -> R): return maxElem } +/** + * Returns the first element having the largest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun Array.maxWith(comparator: Comparator): T? { + if (isEmpty()) return null + var max = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(max, e) < 0) max = e + } + return max +} + +/** + * Returns the first element having the largest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun BooleanArray.maxWith(comparator: Comparator): Boolean? { + if (isEmpty()) return null + var max = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(max, e) < 0) max = e + } + return max +} + +/** + * Returns the first element having the largest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun ByteArray.maxWith(comparator: Comparator): Byte? { + if (isEmpty()) return null + var max = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(max, e) < 0) max = e + } + return max +} + +/** + * Returns the first element having the largest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun CharArray.maxWith(comparator: Comparator): Char? { + if (isEmpty()) return null + var max = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(max, e) < 0) max = e + } + return max +} + +/** + * Returns the first element having the largest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun DoubleArray.maxWith(comparator: Comparator): Double? { + if (isEmpty()) return null + var max = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(max, e) < 0) max = e + } + return max +} + +/** + * Returns the first element having the largest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun FloatArray.maxWith(comparator: Comparator): Float? { + if (isEmpty()) return null + var max = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(max, e) < 0) max = e + } + return max +} + +/** + * Returns the first element having the largest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun IntArray.maxWith(comparator: Comparator): Int? { + if (isEmpty()) return null + var max = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(max, e) < 0) max = e + } + return max +} + +/** + * Returns the first element having the largest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun LongArray.maxWith(comparator: Comparator): Long? { + if (isEmpty()) return null + var max = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(max, e) < 0) max = e + } + return max +} + +/** + * Returns the first element having the largest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun ShortArray.maxWith(comparator: Comparator): Short? { + if (isEmpty()) return null + var max = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(max, e) < 0) max = e + } + return max +} + /** * Returns the smallest element or `null` if there are no elements. */ @@ -8991,6 +9108,123 @@ public inline fun > ShortArray.minBy(selector: (Short) -> R): return minElem } +/** + * Returns the first element having the smallest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun Array.minWith(comparator: Comparator): T? { + if (isEmpty()) return null + var min = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(min, e) > 0) min = e + } + return min +} + +/** + * Returns the first element having the smallest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun BooleanArray.minWith(comparator: Comparator): Boolean? { + if (isEmpty()) return null + var min = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(min, e) > 0) min = e + } + return min +} + +/** + * Returns the first element having the smallest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun ByteArray.minWith(comparator: Comparator): Byte? { + if (isEmpty()) return null + var min = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(min, e) > 0) min = e + } + return min +} + +/** + * Returns the first element having the smallest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun CharArray.minWith(comparator: Comparator): Char? { + if (isEmpty()) return null + var min = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(min, e) > 0) min = e + } + return min +} + +/** + * Returns the first element having the smallest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun DoubleArray.minWith(comparator: Comparator): Double? { + if (isEmpty()) return null + var min = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(min, e) > 0) min = e + } + return min +} + +/** + * Returns the first element having the smallest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun FloatArray.minWith(comparator: Comparator): Float? { + if (isEmpty()) return null + var min = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(min, e) > 0) min = e + } + return min +} + +/** + * Returns the first element having the smallest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun IntArray.minWith(comparator: Comparator): Int? { + if (isEmpty()) return null + var min = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(min, e) > 0) min = e + } + return min +} + +/** + * Returns the first element having the smallest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun LongArray.minWith(comparator: Comparator): Long? { + if (isEmpty()) return null + var min = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(min, e) > 0) min = e + } + return min +} + +/** + * Returns the first element having the smallest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun ShortArray.minWith(comparator: Comparator): Short? { + if (isEmpty()) return null + var min = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(min, e) > 0) min = e + } + return min +} + /** * Returns `true` if the array has no elements. */ diff --git a/libraries/stdlib/src/generated/_Collections.kt b/libraries/stdlib/src/generated/_Collections.kt index 1cf651dcea1..703e3e80861 100644 --- a/libraries/stdlib/src/generated/_Collections.kt +++ b/libraries/stdlib/src/generated/_Collections.kt @@ -1436,6 +1436,20 @@ public inline fun > Iterable.maxBy(selector: (T) -> R): return maxElem } +/** + * Returns the first element having the largest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun Iterable.maxWith(comparator: Comparator): T? { + val iterator = iterator() + if (!iterator.hasNext()) return null + var max = iterator.next() + while (iterator.hasNext()) { + val e = iterator.next() + if (comparator.compare(max, e) < 0) max = e + } + return max +} + /** * Returns the smallest element or `null` if there are no elements. */ @@ -1469,6 +1483,20 @@ public inline fun > Iterable.minBy(selector: (T) -> R): return minElem } +/** + * Returns the first element having the smallest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun Iterable.minWith(comparator: Comparator): T? { + val iterator = iterator() + if (!iterator.hasNext()) return null + var min = iterator.next() + while (iterator.hasNext()) { + val e = iterator.next() + if (comparator.compare(min, e) > 0) min = e + } + return min +} + /** * Returns `true` if the collection has no elements. */ diff --git a/libraries/stdlib/src/generated/_Maps.kt b/libraries/stdlib/src/generated/_Maps.kt index ebdfe22ab39..9c4fdcad24e 100644 --- a/libraries/stdlib/src/generated/_Maps.kt +++ b/libraries/stdlib/src/generated/_Maps.kt @@ -141,6 +141,13 @@ public inline fun > Map.maxBy(selector: (Map.Entry return entries.maxBy(selector) } +/** + * Returns the first entry having the largest value according to the provided [comparator] or `null` if there are no entries. + */ +public fun Map.maxWith(comparator: Comparator>): Map.Entry? { + return entries.maxWith(comparator) +} + /** * Returns the first entry yielding the smallest value of the given function or `null` if there are no entries. */ @@ -148,6 +155,13 @@ public inline fun > Map.minBy(selector: (Map.Entry return entries.minBy(selector) } +/** + * Returns the first entry having the smallest value according to the provided [comparator] or `null` if there are no entries. + */ +public fun Map.minWith(comparator: Comparator>): Map.Entry? { + return entries.minWith(comparator) +} + /** * Returns `true` if the map has no entries. */ diff --git a/libraries/stdlib/src/generated/_Sequences.kt b/libraries/stdlib/src/generated/_Sequences.kt index b14ed63cdf0..8cf431f0da6 100644 --- a/libraries/stdlib/src/generated/_Sequences.kt +++ b/libraries/stdlib/src/generated/_Sequences.kt @@ -837,6 +837,20 @@ public inline fun > Sequence.maxBy(selector: (T) -> R): return maxElem } +/** + * Returns the first element having the largest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun Sequence.maxWith(comparator: Comparator): T? { + val iterator = iterator() + if (!iterator.hasNext()) return null + var max = iterator.next() + while (iterator.hasNext()) { + val e = iterator.next() + if (comparator.compare(max, e) < 0) max = e + } + return max +} + /** * Returns the smallest element or `null` if there are no elements. */ @@ -870,6 +884,20 @@ public inline fun > Sequence.minBy(selector: (T) -> R): return minElem } +/** + * Returns the first element having the smallest value according to the provided [comparator] or `null` if there are no elements. + */ +public fun Sequence.minWith(comparator: Comparator): T? { + val iterator = iterator() + if (!iterator.hasNext()) return null + var min = iterator.next() + while (iterator.hasNext()) { + val e = iterator.next() + if (comparator.compare(min, e) > 0) min = e + } + return min +} + /** * Returns `true` if the sequence has no elements. */ diff --git a/libraries/stdlib/src/generated/_Strings.kt b/libraries/stdlib/src/generated/_Strings.kt index 0531b8cd8b5..0c46d49da25 100644 --- a/libraries/stdlib/src/generated/_Strings.kt +++ b/libraries/stdlib/src/generated/_Strings.kt @@ -1349,6 +1349,19 @@ public inline fun > String.maxBy(selector: (Char) -> R): Char? return maxElem } +/** + * Returns the first character having the largest value according to the provided [comparator] or `null` if there are no characters. + */ +public fun CharSequence.maxWith(comparator: Comparator): Char? { + if (isEmpty()) return null + var max = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(max, e) < 0) max = e + } + return max +} + /** * Returns the smallest character or `null` if there are no characters. */ @@ -1413,6 +1426,19 @@ public inline fun > String.minBy(selector: (Char) -> R): Char? return minElem } +/** + * Returns the first character having the smallest value according to the provided [comparator] or `null` if there are no characters. + */ +public fun CharSequence.minWith(comparator: Comparator): Char? { + if (isEmpty()) return null + var min = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(min, e) > 0) min = e + } + return min +} + /** * Returns `true` if the char sequence has no characters. */ diff --git a/libraries/stdlib/test/OrderingTest.kt b/libraries/stdlib/test/OrderingTest.kt index 60558e8b93f..4422ade7852 100644 --- a/libraries/stdlib/test/OrderingTest.kt +++ b/libraries/stdlib/test/OrderingTest.kt @@ -10,6 +10,8 @@ data class Item(val name: String, val rating: Int) : Comparable { } } +val STRING_CASE_INSENSITIVE_ORDER: Comparator = compareBy { it: String -> it.toUpperCase() }.thenBy { it.toLowerCase() }.thenBy { it } + class OrderingTest { val v1 = Item("wine", 9) val v2 = Item("beer", 10) diff --git a/libraries/stdlib/test/collections/ArraysTest.kt b/libraries/stdlib/test/collections/ArraysTest.kt index 2dac740db18..57fda21c08f 100644 --- a/libraries/stdlib/test/collections/ArraysTest.kt +++ b/libraries/stdlib/test/collections/ArraysTest.kt @@ -1,6 +1,7 @@ package test.collections import test.collections.behaviors.listBehavior +import test.compare.STRING_CASE_INSENSITIVE_ORDER import java.util.* import kotlin.test.* import org.junit.Test as test @@ -146,6 +147,28 @@ class ArraysTest { expect('b', { charArrayOf('a', 'b').max() }) } + @test fun minWith() { + assertEquals(null, arrayOf().minWith(naturalOrder()) ) + assertEquals("a", arrayOf("a", "B").minWith(STRING_CASE_INSENSITIVE_ORDER)) + } + + @test fun minWithInPrimitiveArrays() { + expect(null, { intArrayOf().minWith(naturalOrder()) }) + expect(1, { intArrayOf(1).minWith(naturalOrder()) }) + expect(4, { intArrayOf(2, 3, 4).minWith(compareBy { it % 4 }) }) + } + + @test fun maxWith() { + assertEquals(null, arrayOf().maxWith(naturalOrder()) ) + assertEquals("B", arrayOf("a", "B").maxWith(STRING_CASE_INSENSITIVE_ORDER)) + } + + @test fun maxWithInPrimitiveArrays() { + expect(null, { intArrayOf().maxWith(naturalOrder()) }) + expect(1, { intArrayOf(1).maxWith(naturalOrder()) }) + expect(-4, { intArrayOf(2, 3, -4).maxWith(compareBy { it*it }) }) + } + @test fun minBy() { expect(null, { arrayOf().minBy { it } }) expect(1, { arrayOf(1).minBy { it } }) diff --git a/libraries/stdlib/test/collections/CollectionTest.kt b/libraries/stdlib/test/collections/CollectionTest.kt index a23a175c8ce..ac4aa14820d 100644 --- a/libraries/stdlib/test/collections/CollectionTest.kt +++ b/libraries/stdlib/test/collections/CollectionTest.kt @@ -4,6 +4,7 @@ import java.util.* import kotlin.test.* import org.junit.Test as test import test.collections.behaviors.* +import test.compare.STRING_CASE_INSENSITIVE_ORDER import java.io.Serializable class CollectionTest { @@ -496,6 +497,20 @@ class CollectionTest { expect(3, { listOf(2, 3).asSequence().max() }) } + @test fun minWith() { + expect(null, { listOf().minWith(naturalOrder()) }) + expect(1, { listOf(1).minWith(naturalOrder()) }) + expect("a", { listOf("a", "B").minWith(STRING_CASE_INSENSITIVE_ORDER) }) + expect("a", { listOf("a", "B").asSequence().minWith(STRING_CASE_INSENSITIVE_ORDER) }) + } + + @test fun maxWith() { + expect(null, { listOf().maxWith(naturalOrder()) }) + expect(1, { listOf(1).maxWith(naturalOrder()) }) + expect("B", { listOf("a", "B").maxWith(STRING_CASE_INSENSITIVE_ORDER) }) + expect("B", { listOf("a", "B").asSequence().maxWith(STRING_CASE_INSENSITIVE_ORDER) }) + } + @test fun minBy() { expect(null, { listOf().minBy { it } }) expect(1, { listOf(1).minBy { it } }) diff --git a/libraries/tools/kotlin-stdlib-gen/src/templates/Aggregates.kt b/libraries/tools/kotlin-stdlib-gen/src/templates/Aggregates.kt index 48b40ec1027..8529da319ea 100644 --- a/libraries/tools/kotlin-stdlib-gen/src/templates/Aggregates.kt +++ b/libraries/tools/kotlin-stdlib-gen/src/templates/Aggregates.kt @@ -222,6 +222,36 @@ fun aggregates(): List { body(Maps) { "return entries.minBy(selector)" } } + templates add f("minWith(comparator: Comparator)") { + doc { f -> "Returns the first ${f.element} having the smallest value according to the provided [comparator] or `null` if there are no ${f.element.pluralize()}." } + returns("T?") + body { + """ + val iterator = iterator() + if (!iterator.hasNext()) return null + + var min = iterator.next() + while (iterator.hasNext()) { + val e = iterator.next() + if (comparator.compare(min, e) > 0) min = e + } + return min + """ + } + body(CharSequences, ArraysOfObjects, ArraysOfPrimitives) { + """ + if (isEmpty()) return null + var min = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(min, e) > 0) min = e + } + return min + """ + } + body(Maps) { "return entries.minWith(comparator)" } + } + templates add f("max()") { doc { f -> "Returns the largest ${f.element} or `null` if there are no ${f.element.pluralize()}." } returns("T?") @@ -301,6 +331,37 @@ fun aggregates(): List { body(Maps) { "return entries.maxBy(selector)" } } + templates add f("maxWith(comparator: Comparator)") { + doc { f -> "Returns the first ${f.element} having the largest value according to the provided [comparator] or `null` if there are no ${f.element.pluralize()}." } + returns("T?") + body { + """ + val iterator = iterator() + if (!iterator.hasNext()) return null + + var max = iterator.next() + while (iterator.hasNext()) { + val e = iterator.next() + if (comparator.compare(max, e) < 0) max = e + } + return max + """ + } + body(CharSequences, ArraysOfObjects, ArraysOfPrimitives) { + """ + if (isEmpty()) return null + + var max = this[0] + for (i in 1..lastIndex) { + val e = this[i] + if (comparator.compare(max, e) < 0) max = e + } + return max + """ + } + body(Maps) { "return entries.maxWith(comparator)" } + } + templates add f("fold(initial: R, operation: (R, T) -> R)") { inline(true)