From 536a977f5c08b8fbc16ad2f44adb27a2435a7867 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Mon, 30 Mar 2015 19:57:18 +0300 Subject: [PATCH] indexOfAny, lastIndexOfAny return an index value only (similar to indexOf, lastIndexOf). findAnyOf, findLastAnyOf introduced to cover the case when an index and a matched string value is required. --- libraries/stdlib/src/kotlin/text/Strings.kt | 72 +++++++++++----- libraries/stdlib/test/text/StringTest.kt | 96 ++++++++++++++------- 2 files changed, 113 insertions(+), 55 deletions(-) diff --git a/libraries/stdlib/src/kotlin/text/Strings.kt b/libraries/stdlib/src/kotlin/text/Strings.kt index 9e88756feb2..d509bb35980 100644 --- a/libraries/stdlib/src/kotlin/text/Strings.kt +++ b/libraries/stdlib/src/kotlin/text/Strings.kt @@ -491,8 +491,7 @@ public fun String.endsWith(char: Char): Boolean = endsWith(char, ignoreCase = fa // indexOfAny() - -private fun String.indexOfAny(chars: CharArray, startIndex: Int, ignoreCase: Boolean, last: Boolean): Pair? { +private fun String.findAnyOf(chars: CharArray, startIndex: Int, ignoreCase: Boolean, last: Boolean): Pair? { if (!ignoreCase && chars.size() == 1) { val char = chars.single() val index = if (!last) nativeIndexOf(char, startIndex) else nativeLastIndexOf(char, startIndex) @@ -511,31 +510,31 @@ private fun String.indexOfAny(chars: CharArray, startIndex: Int, ignoreCase: Boo } /** - * Finds the first occurrence of any of the specified [chars] in this string, starting from the specified [startIndex] and + * Finds the index of the first occurrence of any of the specified [chars] in this string, starting from the specified [startIndex] and * optionally ignoring the case. * * @param ignoreCase `true` to ignore character case when matching a character. By default `false`. - * @returns A pair of an index of the first occurrence of matched character from [chars] and the character matched or `null` if none of [chars] are found. + * @returns An index of the first occurrence of matched character from [chars] or -1 if none of [chars] are found. * */ -public fun String.indexOfAny(chars: CharArray, startIndex: Int = 0, ignoreCase: Boolean = false): Pair? = - indexOfAny(chars, startIndex, ignoreCase, last = false) +public fun String.indexOfAny(chars: CharArray, startIndex: Int = 0, ignoreCase: Boolean = false): Int = + findAnyOf(chars, startIndex, ignoreCase, last = false)?.first ?: -1 /** - * Finds the last occurrence of any of the specified [chars] in this string, starting from the specified [startIndex] and + * Finds the index of the last occurrence of any of the specified [chars] in this string, starting from the specified [startIndex] and * optionally ignoring the case. * * @param startIndex The index of character to start searching at. The search proceeds backward toward the beginning of the string. * @param ignoreCase `true` to ignore character case when matching a character. By default `false`. - * @returns A pair of an index of the last occurrence of matched character from [chars] and the character matched or `null` if none of [chars] are found. + * @returns An index of the last occurrence of matched character from [chars] or -1 if none of [chars] are found. * */ -public fun String.lastIndexOfAny(chars: CharArray, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Pair? = - indexOfAny(chars, startIndex, ignoreCase, last = true) +public fun String.lastIndexOfAny(chars: CharArray, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Int = + findAnyOf(chars, startIndex, ignoreCase, last = true)?.first ?: -1 -private fun String.indexOfAny(strings: Collection, startIndex: Int, ignoreCase: Boolean, last: Boolean): Pair? { +private fun String.findAnyOf(strings: Collection, startIndex: Int, ignoreCase: Boolean, last: Boolean): Pair? { if (!ignoreCase && strings.size() == 1) { val string = strings.single() val index = if (!last) nativeIndexOf(string, startIndex) else nativeLastIndexOf(string, startIndex) @@ -557,14 +556,14 @@ private fun String.indexOfAny(strings: Collection, startIndex: Int, igno * optionally ignoring the case. * * @param ignoreCase `true` to ignore character case when matching a string. By default `false`. - * @returns A pair of an index of the first occurrence of matched string from [stromgs] and the string matched or `null` if none of [strings] are found. + * @returns A pair of an index of the first occurrence of matched string from [strings] and the string matched or `null` if none of [strings] are found. * * To avoid ambiguous results when strings in [strings] have characters in common, this method proceeds from * the beginning to the end of this string, and finds at each position the first element in [strings] * that matches this string at that position. */ -public fun String.indexOfAny(strings: Collection, startIndex: Int = 0, ignoreCase: Boolean = false): Pair? = - indexOfAny(strings, startIndex, ignoreCase, last = false) +public fun String.findAnyOf(strings: Collection, startIndex: Int = 0, ignoreCase: Boolean = false): Pair? = + findAnyOf(strings, startIndex, ignoreCase, last = false) /** * Finds the last occurrence of any of the specified [strings] in this string, starting from the specified [startIndex] and @@ -578,8 +577,37 @@ public fun String.indexOfAny(strings: Collection, startIndex: Int = 0, i * the end toward the beginning of this string, and finds at each position the first element in [strings] * that matches this string at that position. */ -public fun String.lastIndexOfAny(strings: Collection, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Pair? = - indexOfAny(strings, startIndex, ignoreCase, last = true) +public fun String.findLastAnyOf(strings: Collection, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Pair? = + findAnyOf(strings, startIndex, ignoreCase, last = true) + +/** + * Finds the index of the first occurrence of any of the specified [strings] in this string, starting from the specified [startIndex] and + * optionally ignoring the case. + * + * @param ignoreCase `true` to ignore character case when matching a string. By default `false`. + * @returns An index of the first occurrence of matched string from [strings] or -1 if none of [strings] are found. + * + * To avoid ambiguous results when strings in [strings] have characters in common, this method proceeds from + * the beginning to the end of this string, and finds at each position the first element in [strings] + * that matches this string at that position. + */ +public fun String.indexOfAny(strings: Collection, startIndex: Int = 0, ignoreCase: Boolean = false): Int = + findAnyOf(strings, startIndex, ignoreCase, last = false)?.first ?: -1 + +/** + * Finds the index of the last occurrence of any of the specified [strings] in this string, starting from the specified [startIndex] and + * optionally ignoring the case. + * + * @param startIndex The index of character to start searching at. The search proceeds backward toward the beginning of the string. + * @param ignoreCase `true` to ignore character case when matching a string. By default `false`. + * @returns An index of the last occurrence of matched string from [strings] or -1 if none of [strings] are found. + * + * To avoid ambiguous results when strings in [strings] have characters in common, this method proceeds from + * the end toward the beginning of this string, and finds at each position the first element in [strings] + * that matches this string at that position. + */ +public fun String.lastIndexOfAny(strings: Collection, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Int = + findAnyOf(strings, startIndex, ignoreCase, last = true)?.first ?: -1 // indexOf @@ -592,7 +620,7 @@ public fun String.lastIndexOfAny(strings: Collection, startIndex: Int = */ public fun String.indexOf(char: Char, startIndex: Int = 0, ignoreCase: Boolean = false): Int { return if (ignoreCase) - indexOfAny(charArray(char), startIndex, ignoreCase)?.first ?: -1 + indexOfAny(charArray(char), startIndex, ignoreCase) else nativeIndexOf(char, startIndex) } @@ -605,7 +633,7 @@ public fun String.indexOf(char: Char, startIndex: Int = 0, ignoreCase: Boolean = */ public fun String.indexOf(string: String, startIndex: Int = 0, ignoreCase: Boolean = false): Int { return if (ignoreCase) - indexOfAny(listOf(string), startIndex, ignoreCase)?.first ?: -1 + indexOfAny(listOf(string), startIndex, ignoreCase) else nativeIndexOf(string, startIndex) } @@ -619,7 +647,7 @@ public fun String.indexOf(string: String, startIndex: Int = 0, ignoreCase: Boole */ public fun String.lastIndexOf(char: Char, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Int { return if (ignoreCase) - lastIndexOfAny(charArray(char), startIndex, ignoreCase)?.first ?: -1 + lastIndexOfAny(charArray(char), startIndex, ignoreCase) else nativeLastIndexOf(char, startIndex) } @@ -633,7 +661,7 @@ public fun String.lastIndexOf(char: Char, startIndex: Int = lastIndex, ignoreCas */ public fun String.lastIndexOf(string: String, startIndex: Int = lastIndex, ignoreCase: Boolean = false): Int { return if (ignoreCase) - lastIndexOfAny(listOf(string), startIndex, ignoreCase)?.first ?: -1 + lastIndexOfAny(listOf(string), startIndex, ignoreCase) else nativeLastIndexOf(string, startIndex) } @@ -717,7 +745,7 @@ private class DelimitedRangesSequence(private val string: String, private val st private fun String.rangesDelimitedBy(vararg delimiters: Char, startIndex: Int = 0, ignoreCase: Boolean = false, limit: Int = 0): Sequence { require(limit >= 0, { "Limit must be non-negative, but was $limit" }) - return DelimitedRangesSequence(this, startIndex, limit, { startIndex -> indexOfAny(delimiters, startIndex, ignoreCase = ignoreCase)?.let { Pair(it.first, 1) }}) + return DelimitedRangesSequence(this, startIndex, limit, { startIndex -> findAnyOf(delimiters, startIndex, ignoreCase = ignoreCase, last = false)?.let { it.first to 1 } }) } @@ -739,7 +767,7 @@ private fun String.rangesDelimitedBy(vararg delimiters: String, startIndex: Int require(limit >= 0, { "Limit must be non-negative, but was $limit" } ) val delimitersList = delimiters.asList() - return DelimitedRangesSequence(this, startIndex, limit, { startIndex -> indexOfAny(delimitersList, startIndex, ignoreCase = ignoreCase)?.let { Pair(it.first, it.second.length() )} }) + return DelimitedRangesSequence(this, startIndex, limit, { startIndex -> findAnyOf(delimitersList, startIndex, ignoreCase = ignoreCase, last = false)?.let { it.first to it.second.length ()} }) } diff --git a/libraries/stdlib/test/text/StringTest.kt b/libraries/stdlib/test/text/StringTest.kt index 394ce0686f4..77183630866 100644 --- a/libraries/stdlib/test/text/StringTest.kt +++ b/libraries/stdlib/test/text/StringTest.kt @@ -315,57 +315,87 @@ class StringTest { test fun indexOfAnyChar() { val string = "abracadabra" val chars = charArray('d','b') - assertEquals(1 to 'b', string.indexOfAny(chars)) - assertEquals(6 to 'd', string.indexOfAny(chars, startIndex = 2)) - assertEquals(null, string.indexOfAny(chars, startIndex = 9)) + assertEquals(1, string.indexOfAny(chars)) + assertEquals(6, string.indexOfAny(chars, startIndex = 2)) + assertEquals(-1, string.indexOfAny(chars, startIndex = 9)) - assertEquals(8 to 'b', string.lastIndexOfAny(chars)) - assertEquals(6 to 'd', string.lastIndexOfAny(chars, startIndex = 7)) - assertEquals(null, string.lastIndexOfAny(chars, startIndex = 0)) + assertEquals(8, string.lastIndexOfAny(chars)) + assertEquals(6, string.lastIndexOfAny(chars, startIndex = 7)) + assertEquals(-1, string.lastIndexOfAny(chars, startIndex = 0)) - assertEquals(null, string.indexOfAny(charArray())) + assertEquals(-1, string.indexOfAny(charArray())) } test fun indexOfAnyCharIgnoreCase() { val string = "abraCadabra" val chars = charArray('B','c') - assertEquals(1 to 'B', string.indexOfAny(chars, ignoreCase = true)) - assertEquals(4 to 'c', string.indexOfAny(chars, startIndex = 2, ignoreCase = true)) - assertEquals(null, string.indexOfAny(chars, startIndex = 9, ignoreCase = true)) + assertEquals(1, string.indexOfAny(chars, ignoreCase = true)) + assertEquals(4, string.indexOfAny(chars, startIndex = 2, ignoreCase = true)) + assertEquals(-1, string.indexOfAny(chars, startIndex = 9, ignoreCase = true)) - assertEquals(8 to 'B', string.lastIndexOfAny(chars, ignoreCase = true)) - assertEquals(4 to 'c', string.lastIndexOfAny(chars, startIndex = 7, ignoreCase = true)) - assertEquals(null, string.lastIndexOfAny(chars, startIndex = 0, ignoreCase = true)) + assertEquals(8, string.lastIndexOfAny(chars, ignoreCase = true)) + assertEquals(4, string.lastIndexOfAny(chars, startIndex = 7, ignoreCase = true)) + assertEquals(-1, string.lastIndexOfAny(chars, startIndex = 0, ignoreCase = true)) } test fun indexOfAnyString() { val string = "abracadabra" val substrings = listOf("rac", "ra") - assertEquals(2 to "rac", string.indexOfAny(substrings)) - assertEquals(9 to "ra", string.indexOfAny(substrings, startIndex = 3)) - assertEquals(2 to "ra", string.indexOfAny(substrings.reverse())) - assertEquals(null, string.indexOfAny(substrings, 10)) + assertEquals(2, string.indexOfAny(substrings)) + assertEquals(9, string.indexOfAny(substrings, startIndex = 3)) + assertEquals(2, string.indexOfAny(substrings.reverse())) + assertEquals(-1, string.indexOfAny(substrings, 10)) - assertEquals(9 to "ra", string.lastIndexOfAny(substrings)) - assertEquals(2 to "rac", string.lastIndexOfAny(substrings, startIndex = 8)) - assertEquals(2 to "ra", string.lastIndexOfAny(substrings.reverse(), startIndex = 8)) - assertEquals(null, string.lastIndexOfAny(substrings, 1)) + assertEquals(9, string.lastIndexOfAny(substrings)) + assertEquals(2, string.lastIndexOfAny(substrings, startIndex = 8)) + assertEquals(2, string.lastIndexOfAny(substrings.reverse(), startIndex = 8)) + assertEquals(-1, string.lastIndexOfAny(substrings, 1)) - assertEquals(0 to "", string.indexOfAny(listOf("dab", "")), "empty strings are not ignored") - assertEquals(null, string.indexOfAny(listOf())) + assertEquals(0, string.indexOfAny(listOf("dab", "")), "empty strings are not ignored") + assertEquals(-1, string.indexOfAny(listOf())) } test fun indexOfAnyStringIgnoreCase() { val string = "aBraCadaBrA" val substrings = listOf("rAc", "Ra") - assertEquals(2 to substrings[0], string.indexOfAny(substrings, ignoreCase = true)) - assertEquals(9 to substrings[1], string.indexOfAny(substrings, startIndex = 3, ignoreCase = true)) - assertEquals(null, string.indexOfAny(substrings, startIndex = 10, ignoreCase = true)) + assertEquals(2, string.indexOfAny(substrings, ignoreCase = true)) + assertEquals(9, string.indexOfAny(substrings, startIndex = 3, ignoreCase = true)) + assertEquals(-1, string.indexOfAny(substrings, startIndex = 10, ignoreCase = true)) - assertEquals(9 to substrings[1], string.lastIndexOfAny(substrings, ignoreCase = true)) - assertEquals(2 to substrings[0], string.lastIndexOfAny(substrings, startIndex = 8, ignoreCase = true)) - assertEquals(null, string.lastIndexOfAny(substrings, startIndex = 1, ignoreCase = true)) + assertEquals(9, string.lastIndexOfAny(substrings, ignoreCase = true)) + assertEquals(2, string.lastIndexOfAny(substrings, startIndex = 8, ignoreCase = true)) + assertEquals(-1, string.lastIndexOfAny(substrings, startIndex = 1, ignoreCase = true)) + } + + test fun findAnyOfStrings() { + val string = "abracadabra" + val substrings = listOf("rac", "ra") + assertEquals(2 to "rac", string.findAnyOf(substrings)) + assertEquals(9 to "ra", string.findAnyOf(substrings, startIndex = 3)) + assertEquals(2 to "ra", string.findAnyOf(substrings.reverse())) + assertEquals(null, string.findAnyOf(substrings, 10)) + + assertEquals(9 to "ra", string.findLastAnyOf(substrings)) + assertEquals(2 to "rac", string.findLastAnyOf(substrings, startIndex = 8)) + assertEquals(2 to "ra", string.findLastAnyOf(substrings.reverse(), startIndex = 8)) + assertEquals(null, string.findLastAnyOf(substrings, 1)) + + assertEquals(0 to "", string.findAnyOf(listOf("dab", "")), "empty strings are not ignored") + assertEquals(null, string.findAnyOf(listOf())) + } + + test fun findAnyOfStringsIgnoreCase() { + val string = "aBraCadaBrA" + val substrings = listOf("rAc", "Ra") + + assertEquals(2 to substrings[0], string.findAnyOf(substrings, ignoreCase = true)) + assertEquals(9 to substrings[1], string.findAnyOf(substrings, startIndex = 3, ignoreCase = true)) + assertEquals(null, string.findAnyOf(substrings, startIndex = 10, ignoreCase = true)) + + assertEquals(9 to substrings[1], string.findLastAnyOf(substrings, ignoreCase = true)) + assertEquals(2 to substrings[0], string.findLastAnyOf(substrings, startIndex = 8, ignoreCase = true)) + assertEquals(null, string.findLastAnyOf(substrings, startIndex = 1, ignoreCase = true)) } test fun indexOfChar() { @@ -378,8 +408,8 @@ class StringTest { assertEquals(2, string.lastIndexOf('e', 3)) for (startIndex in -1..string.length()+1) { - assertEquals(string.indexOfAny(charArray('e'), startIndex)?.first ?: -1, string.indexOf('e', startIndex)) - assertEquals(string.lastIndexOfAny(charArray('e'), startIndex)?.first ?: -1, string.lastIndexOf('e', startIndex)) + assertEquals(string.indexOfAny(charArray('e'), startIndex), string.indexOf('e', startIndex)) + assertEquals(string.lastIndexOfAny(charArray('e'), startIndex), string.lastIndexOf('e', startIndex)) } } @@ -395,8 +425,8 @@ class StringTest { for (startIndex in -1..string.length()+1){ - assertEquals(string.indexOfAny(charArray('e'), startIndex, ignoreCase = true)?.first ?: -1, string.indexOf('E', startIndex, ignoreCase = true)) - assertEquals(string.lastIndexOfAny(charArray('E'), startIndex, ignoreCase = true)?.first ?: -1, string.lastIndexOf('e', startIndex, ignoreCase = true)) + assertEquals(string.indexOfAny(charArray('e'), startIndex, ignoreCase = true), string.indexOf('E', startIndex, ignoreCase = true)) + assertEquals(string.lastIndexOfAny(charArray('E'), startIndex, ignoreCase = true), string.lastIndexOf('e', startIndex, ignoreCase = true)) } }