Provide String.commonPrefixWith and String.commonSuffixWith methods.

Char.isHighSurrogate and Char.isLowSurrogate now available in js-stdlib too.
This commit is contained in:
Ilya Gorbunov
2015-04-03 21:35:56 +03:00
parent 70cc7fbf1f
commit 7cac6e3c5e
5 changed files with 129 additions and 9 deletions
+35
View File
@@ -39,3 +39,38 @@ public fun Char.equals(other: Char, ignoreCase: Boolean = false): Boolean {
if (this.toLowerCase() === other.toLowerCase()) return true
return false
}
/**
* Returns `true` if this character is a Unicode high-surrogate code unit (also known as leading-surrogate code unit).
*/
public fun Char.isHighSurrogate(): Boolean = this in MIN_HIGH_SURROGATE..MAX_HIGH_SURROGATE
/**
* Returns `true` if this character is a Unicode low-surrogate code unit (also known as trailing-surrogate code unit).
*/
public fun Char.isLowSurrogate(): Boolean = this in MIN_LOW_SURROGATE..MAX_LOW_SURROGATE
// TODO: make these constants public in Char.Companion after resolving #KT-7271
/**
* The minimum value of a Unicode high-surrogate code unit
*/
private val MIN_HIGH_SURROGATE: Char
get() = '\uD800'
/**
* The maximum value of a Unicode high-surrogate code unit
*/
private val MAX_HIGH_SURROGATE: Char
get() = '\uDBFF'
/**
* The minimum value of a Unicode low-surrogate code unit
*/
private val MIN_LOW_SURROGATE: Char
get() = '\uDC00'
/**
* The maximum value of a Unicode low-surrogate code unit
*/
private val MAX_LOW_SURROGATE: Char
get() = '\uDFFF'
@@ -26,10 +26,6 @@ public fun Char.isDefined(): Boolean = Character.isDefined(this)
*/
public fun Char.isDigit(): Boolean = Character.isDigit(this)
/**
* Returns `true` if this character is a Unicode high-surrogate code unit (also known as leading-surrogate code unit).
*/
public fun Char.isHighSurrogate(): Boolean = Character.isHighSurrogate(this)
/**
* Returns `true` if this character (Unicode code point) should be regarded as an ignorable
+58 -3
View File
@@ -187,6 +187,12 @@ public fun String?.orEmpty(): String = this ?: ""
public val String.indices: IntRange
get() = 0..length() - 1
/**
* Returns the index of the last character in the String or -1 if the String is empty
*/
public val String.lastIndex: Int
get() = this.length() - 1
/**
* Returns a character at the given index in a [CharSequence]. Allows to use the
* index operator for working with character sequences:
@@ -197,10 +203,13 @@ public val String.indices: IntRange
public fun CharSequence.get(index: Int): Char = this.charAt(index)
/**
* Returns the index of the last character in the String or -1 if the String is empty
* Returns `true` if this CharSequence has Unicode surrogate pair at the specified [index]
*/
public val String.lastIndex: Int
get() = this.length() - 1
public fun CharSequence.hasSurrogatePairAt(index: Int): Boolean {
return index in 0..length() - 2
&& this[index].isHighSurrogate()
&& this[index + 1].isLowSurrogate()
}
/**
* Returns a subsequence obtained by taking the characters at the given [indices] in this sequence.
@@ -508,6 +517,52 @@ public fun String.endsWith(char: Char, ignoreCase: Boolean): Boolean =
// TODO: temporary overload to keep binary compatibility, remove after fixing markdown parser
public fun String.endsWith(char: Char): Boolean = endsWith(char, ignoreCase = false)
// common prefix and suffix
/**
* Returns the longest string `prefix` such that this string and [other] string both start with this prefix,
* taking care not to split surrogate pairs.
* If this and [other] have no common prefix, returns the empty string.
* @param ignoreCase `true` to ignore character case when matching a character. By default `false`.
*/
public fun CharSequence.commonPrefixWith(other: CharSequence, ignoreCase: Boolean = false): String {
val shortestLength = Math.min(this.length(), other.length())
var i = 0
while (i < shortestLength && this[i].equals(other[i], ignoreCase = ignoreCase)) {
i++
}
if (this.hasSurrogatePairAt(i - 1) || other.hasSurrogatePairAt(i - 1)) {
i--
}
return subSequence(0, i).toString()
}
/**
* Returns the longest string `suffix` such that this string and [other] string both end with this suffix,
* taking care not to split surrogate pairs.
* If this and [other] have no common suffix, returns the empty string.
* @param ignoreCase `true` to ignore character case when matching a character. By default `false`.
*/
public fun CharSequence.commonSuffixWith(other: CharSequence, ignoreCase: Boolean = false): String {
val thisLength = this.length()
val otherLength = other.length()
val shortestLength = Math.min(thisLength, otherLength)
var i = 0
while (i < shortestLength && this[thisLength - i - 1].equals(other[otherLength - i - 1], ignoreCase = ignoreCase)) {
i++
}
if (this.hasSurrogatePairAt(thisLength - i - 1) || other.hasSurrogatePairAt(otherLength - i - 1)) {
i--;
}
return subSequence(thisLength - i, thisLength).toString();
}
// indexOfAny()
private fun String.findAnyOf(chars: CharArray, startIndex: Int, ignoreCase: Boolean, last: Boolean): Pair<Int, Char>? {
@@ -40,11 +40,12 @@ public fun String.equalsIgnoreCase(anotherString: String): Boolean = equals(anot
*
* @param ignoreCase `true` to ignore character case when comparing strings. By default `false`.
*/
public fun String.equals(anotherString: String, ignoreCase: Boolean = false): Boolean =
if (!ignoreCase)
public fun String.equals(anotherString: String, ignoreCase: Boolean = false): Boolean {
return if (!ignoreCase)
(this as java.lang.String).equals(anotherString)
else
(this as java.lang.String).equalsIgnoreCase(anotherString)
}
/**
* Returns a copy of this string with all occurrences of [oldChar] replaced with [newChar].
+33
View File
@@ -70,6 +70,39 @@ class StringTest {
assertFalse("".endsWith('a'))
}
test fun commonPrefix() {
assertEquals("", "".commonPrefixWith(""))
assertEquals("", "any".commonPrefixWith(""))
assertEquals("", "".commonPrefixWith("any"))
assertEquals("", "some".commonPrefixWith("any"))
assertEquals("an", "annual".commonPrefixWith("any"))
assertEquals("an", "annual".commonPrefixWith("Any", ignoreCase = true))
assertEquals("", "annual".commonPrefixWith("Any", ignoreCase = false))
// surrogate pairs
val dth54 = "\uD83C\uDC58" // domino tile horizontal 5-4
val dth55 = "\uD83C\uDC59" // domino tile horizontal 5-5
assertEquals("", dth54.commonPrefixWith(dth55))
assertEquals(dth54, "$dth54$dth54".commonPrefixWith("$dth54$dth55"))
}
test fun commonSuffix() {
assertEquals("", "".commonSuffixWith(""))
assertEquals("", "any".commonSuffixWith(""))
assertEquals("", "".commonSuffixWith("any"))
assertEquals("", "some".commonSuffixWith("any"))
assertEquals("ly", "yearly".commonSuffixWith("monthly"))
assertEquals("strö", "strö".commonSuffixWith("BISTRÖ", ignoreCase = true))
assertEquals("", "yearly".commonSuffixWith("HARDLY", ignoreCase = false))
// surrogate pairs
val dth54 = "\uD83C\uDC58" // domino tile horizontal 5-4
val kimono = "\uD83D\uDC58" // kimono
assertEquals("", dth54.commonSuffixWith(kimono))
assertEquals("$dth54", "d$dth54".commonSuffixWith("s$dth54"))
}
test fun capitalize() {
assertEquals("A", "A".capitalize())
assertEquals("A", "a".capitalize())