diff --git a/libraries/stdlib/src/kotlin/util/Ordering.kt b/libraries/stdlib/src/kotlin/util/Ordering.kt index 0bc11681934..3c3c25e0307 100644 --- a/libraries/stdlib/src/kotlin/util/Ordering.kt +++ b/libraries/stdlib/src/kotlin/util/Ordering.kt @@ -68,18 +68,23 @@ public inline fun compareValuesBy(a: T, b: T, selector: (T) -> Comparable<*> return compareValues(selector(a), selector(b)) } +/** + * Compares two values using the specified [selector] function to calculate the result of the comparison. + * The function is applied to the given values [a] and [b] and return objects of type K which are then being + * compared with the given [comparator]. + */ public inline fun compareValuesBy(a: T, b: T, comparator: Comparator, selector: (T) -> K): Int { return comparator.compare(selector(a), selector(b)) } -// Not so useful without type inference for receiver of expression -// compareValuesWith(v1, v2, compareBy { it.prop1 } thenByDescending { it.prop2 }) -/** - * Compares two values using the specified [comparator]. - */ -@suppress("NOTHING_TO_INLINE") -public inline fun compareValuesWith(a: T, b: T, comparator: Comparator): Int = comparator.compare(a, b) - +//// Not so useful without type inference for receiver of expression +//// compareValuesWith(v1, v2, compareBy { it.prop1 } thenByDescending { it.prop2 }) +///** +// * Compares two values using the specified [comparator]. +// */ +//@suppress("NOTHING_TO_INLINE") +//public inline fun compareValuesWith(a: T, b: T, comparator: Comparator): Int = comparator.compare(a, b) +// /** @@ -120,7 +125,11 @@ inline public fun compareBy(inlineOptions(InlineOption.ONLY_LOCAL_RETURN) co } } -public fun compareBy(comparator: Comparator, selector: (T) -> K): Comparator { +/** + * Creates a comparator using the [selector] function to transform values being compared and then applying + * the specified [comparator] to compare transformed values. + */ +inline public fun compareBy(comparator: Comparator, inlineOptions(InlineOption.ONLY_LOCAL_RETURN) selector: (T) -> K): Comparator { return object : Comparator { public override fun compare(a: T, b: T): Int = compareValuesBy(a, b, comparator, selector) } @@ -136,7 +145,19 @@ inline public fun compareByDescending(inlineOptions(InlineOption.ONLY_LOCAL_ } /** - * Creates a comparator using the primary comparator and + * Creates a descending comparator using the [selector] function to transform values being compared and then applying + * the specified [comparator] to compare transformed values. + * + * Note that an order of [comparator] is reversed by this wrapper. + */ +inline public fun compareByDescending(comparator: Comparator, inlineOptions(InlineOption.ONLY_LOCAL_RETURN) selector: (T) -> K): Comparator { + return object : Comparator { + public override fun compare(a: T, b: T): Int = compareValuesBy(b, a, comparator, selector) + } +} + +/** + * Creates a comparator comparing values after the primary comparator defined them equal. It uses * the function to transform value to a [Comparable] instance for comparison. */ inline public fun Comparator.thenBy(inlineOptions(InlineOption.ONLY_LOCAL_RETURN) comparable: (T) -> Comparable<*>?): Comparator { @@ -148,6 +169,19 @@ inline public fun Comparator.thenBy(inlineOptions(InlineOption.ONLY_LOCAL } } +/** + * Creates a comparator comparing values after the primary comparator defined them equal. It uses + * the [selector] function to transform values and then compares them with the given [comparator]. + */ +inline public fun Comparator.thenBy(comparator: Comparator, inlineOptions(InlineOption.ONLY_LOCAL_RETURN) selector: (T) -> K): Comparator { + return object : Comparator { + public override fun compare(a: T, b: T): Int { + val previousCompare = this@thenBy.compare(a, b) + return if (previousCompare != 0) previousCompare else compareValuesBy(a, b, comparator, selector) + } + } +} + /** * Creates a descending comparator using the primary comparator and * the function to transform value to a [Comparable] instance for comparison. @@ -161,6 +195,20 @@ inline public fun Comparator.thenByDescending(inlineOptions(InlineOption. } } +/** + * Creates a descending comparator comparing values after the primary comparator defined them equal. It uses + * the [selector] function to transform values and then compares them with the given [comparator]. + */ +inline public fun Comparator.thenByDescending(comparator: Comparator, inlineOptions(InlineOption.ONLY_LOCAL_RETURN) selector: (T) -> K): Comparator { + return object : Comparator { + public override fun compare(a: T, b: T): Int { + val previousCompare = this@thenByDescending.compare(a, b) + return if (previousCompare != 0) previousCompare else compareValuesBy(b, a, comparator, selector) + } + } +} + + /** * Creates a comparator using the function to calculate a result of comparison. */ @@ -182,10 +230,36 @@ inline public fun Comparator.thenComparator(inlineOptions(InlineOption.ON } } +/** + * Combines this comparator and the given [comparator] such that the latter is applied only + * when the former considered values equal. + */ +public fun Comparator.then(comparator: Comparator): Comparator { + return object : Comparator { + public override fun compare(a: T, b: T): Int { + val previousCompare = this@then.compare(a, b) + return if (previousCompare != 0) previousCompare else comparator.compare(a, b) + } + } +} + +/** + * Combines this comparator and the given [comparator] such that the latter is applied only + * when the former considered values equal. + */ +public fun Comparator.thenDescending(comparator: Comparator): Comparator { + return object : Comparator { + public override fun compare(a: T, b: T): Int { + val previousCompare = this@thenDescending.compare(a, b) + return if (previousCompare != 0) previousCompare else comparator.compare(b, a) + } + } +} + // Not so useful without type inference for receiver of expression /** * Extends the given [comparator] of non-nullable values to a comparator of nullable values - * considering null value less than any other value. + * considering `null` value less than any other value. */ public fun nullsFirst(comparator: Comparator): Comparator { return object: Comparator { @@ -198,9 +272,24 @@ public fun nullsFirst(comparator: Comparator): Comparator { } } +/** + * Provides a comparator of nullable [Comparable] values + * considering `null` value less than any other value. + */ +public fun > nullsFirst(): Comparator { + return object: Comparator { + override fun compare(a: T?, b: T?): Int { + if (a === b) return 0 + if (a == null) return -1 + if (b == null) return 1 + return a.compareTo(b) + } + } +} + /** * Extends the given [comparator] of non-nullable values to a comparator of nullable values - * considering null value greater than any other value. + * considering `null` value greater than any other value. */ public fun nullsLast(comparator: Comparator): Comparator { return object: Comparator { @@ -211,4 +300,19 @@ public fun nullsLast(comparator: Comparator): Comparator { return comparator.compare(a, b) } } +} + +/** + * Provides a comparator of nullable [Comparable] values + * considering `null` value greater than any other value. + */ +public fun > nullsLast(): Comparator { + return object: Comparator { + override fun compare(a: T?, b: T?): Int { + if (a === b) return 0 + if (a == null) return 1 + if (b == null) return -1 + return a.compareTo(b) + } + } } \ No newline at end of file diff --git a/libraries/stdlib/test/OrderingTest.kt b/libraries/stdlib/test/OrderingTest.kt index c0ad41c054a..88af1588180 100644 --- a/libraries/stdlib/test/OrderingTest.kt +++ b/libraries/stdlib/test/OrderingTest.kt @@ -53,6 +53,20 @@ class OrderingTest { assertEquals(v1, items[1]) } + Test fun combineComparators() { + val byName = compareBy { it.name } + val byRating = compareBy { it.rating } + val v3 = Item(v1.name, v1.rating + 1) + val v4 = Item(v2.name + "_", v2.rating) + assertTrue( (byName then byRating).compare(v1, v2) > 0 ) + assertTrue( (byName then byRating).compare(v1, v3) < 0 ) + assertTrue( (byName thenDescending byRating).compare(v1, v3) > 0 ) + + assertTrue( (byRating then byName).compare(v1, v2) < 0 ) + assertTrue( (byRating then byName).compare(v4, v2) > 0 ) + assertTrue( (byRating thenDescending byName).compare(v4, v2) < 0 ) + } + Test fun sortByThenBy() { val comparator = compareBy { it.rating } thenBy { it.name } diff --git a/libraries/stdlib/test/collections/CollectionTest.kt b/libraries/stdlib/test/collections/CollectionTest.kt index 8450bbebfcd..ed7a558b3da 100644 --- a/libraries/stdlib/test/collections/CollectionTest.kt +++ b/libraries/stdlib/test/collections/CollectionTest.kt @@ -584,7 +584,7 @@ class CollectionTest { listOf("", "sort", "abc").let { assertEquals(listOf("", "abc", "sort"), it.sortedBy { it.nonEmptyLength() }) assertEquals(listOf("sort", "abc", ""), it.sortedByDescending { it.nonEmptyLength() }) - assertEquals(listOf("abc", "sort", ""), it.sortedWith(compareBy(nullsLast(compareBy {it})) { it.nonEmptyLength()})) + assertEquals(listOf("abc", "sort", ""), it.sortedWith(compareBy(nullsLast()) { it.nonEmptyLength()})) } }