[KT-1859] Added collection like extension methods to String.

This commit is contained in:
Yuri Samsoniuk
2013-01-14 21:44:17 +02:00
committed by Evgeny Gerashchenko
parent 37b12bff2e
commit b9e07eb645
2 changed files with 466 additions and 1 deletions
+302
View File
@@ -1,6 +1,9 @@
package kotlin
import java.util.ArrayList
import java.util.HashMap
import java.util.HashSet
import java.util.LinkedList
/** Returns the string with leading and trailing text matching the given string removed */
public inline fun String.trim(text: String) : String = trimLeading(text).trimTrailing(text)
@@ -63,3 +66,302 @@ public inline fun String.count(predicate: (Char) -> Boolean): Int {
}
return answer
}
/**
* Filters characters which match the given predicate into new String object
*
* @includeFunctionBody ../../test/StringTest.kt filter
*/
public inline fun String.filter(predicate: (Char) -> Boolean): String = filterTo(StringBuilder(), predicate).toString()
/**
* Returns an Appendable containing all characters which match the given *predicate*
*
* @includeFunctionBody ../../test/StringTest.kt filter
*/
public inline fun <T: Appendable> String.filterTo(result: T, predicate: (Char) -> Boolean): T
{
for (с in this) if (predicate(с)) result.append(с)
return result
}
/**
* Filters characters which match the given predicate into new String object
*
* @includeFunctionBody ../../test/StringTest.kt filterNot
*/
public inline fun String.filterNot(predicate: (Char) -> Boolean): String = filterNotTo(StringBuilder(), predicate).toString()
/**
* Returns an Appendable containing all characters which do not match the given *predicate*
*
* @includeFunctionBody ../../test/StringTest.kt filterNot
*/
public inline fun <T: Appendable> String.filterNotTo(result: T, predicate: (Char) -> Boolean): T {
for (element in this) if (!predicate(element)) result.append(element)
return result
}
/**
* Returns order of characters into a string
*
* @includeFunctionBody ../../test/StringTest.kt reverse
*/
public inline fun String.reverse(): String = StringBuilder(this).reverse().toString()
/**
* Performs the given *operation* on each character
*
* @includeFunctionBody ../../test/StringTest.kt forEach
*/
public inline fun String.forEach(operation: (Char) -> Unit): Unit = for(c in this) operation(c)
/**
* Returns *true* if all characters match the given *predicate*
*
* @includeFunctionBody ../../test/StringTest.kt all
*/
public inline fun String.all(predicate: (Char) -> Boolean): Boolean {
for(c in this) if(!predicate(c)) return false
return true
}
/**
* Returns *true* if any character matches the given *predicate*
*
* @includeFunctionBody ../../test/StringTest.kt any
*/
public inline fun String.any(predicate: (Char) -> Boolean): Boolean {
for (c in this) if (predicate(c)) return true
return false
}
/**
* Appends the string from all the characters separated using the *separator* and using the given *prefix* and *postfix* if supplied
*
* If a string could be huge you can specify a non-negative value of *limit* which will only show substring then it will
* a special *truncated* separator (which defaults to "..."
*
* @includeFunctionBody ../../test/StringTest.kt appendString
*/
public inline fun String.appendString(buffer: Appendable, separator: String = ", ", prefix: String = "", postfix: String = "", limit: Int = -1, truncated: String = "..."): Unit {
buffer.append(prefix)
var count = 0
for (c in this) {
if (++count > 1) buffer.append(separator)
if (limit < 0 || count <= limit) buffer.append(c) else break
}
if (limit >= 0 && count > limit) buffer.append(truncated)
buffer.append(postfix)
}
/**
* Returns the first character which matches the given *predicate* or *null* if none matched
*
* @includeFunctionBody ../../test/StringTest.kt find
*/
public inline fun String.find(predicate: (Char) -> Boolean): Char? {
for (c in this) if (predicate(c)) return c
return null
}
/**
* Returns the first character which does not match the given *predicate* or *null* if none matched
*
* @includeFunctionBody ../../test/StringTest.kt findNot
*/
public inline fun String.findNot(predicate: (Char) -> Boolean): Char? {
for (c in this) if (!predicate(c)) return c
return null
}
/**
* Partitions this string into a pair of string
*
* @includeFunctionBody ../../test/StringTest.kt partition
*/
public inline fun String.partition(predicate: (Char) -> Boolean): Pair<String, String> {
val first = StringBuilder()
val second = StringBuilder()
for (c in this) {
if (predicate(c)) {
first.append(c)
} else {
second.append(c)
}
}
return Pair(first.toString(), second.toString())
}
/**
* Returns the result of transforming each character to one or more values which are concatenated together into a single list
*
* @includeFunctionBody ../../test/StringTest.kt flatMap
*/
public inline fun <R> String.flatMap(transform: (Char) -> Collection<R>): Collection<R> = flatMapTo(ArrayList<R>(), transform)
/**
* Returns the result of transforming each character to one or more values which are concatenated together into a passed list
*
* @includeFunctionBody ../../test/StringTest.kt flatMap
*/
public inline fun <R> String.flatMapTo(result: MutableCollection<R>, transform: (Char) -> Collection<R>): Collection<R> {
for (c in this) result.addAll(transform(c))
return result
}
/**
* Folds all characters from left to right with the *initial* value to perform the operation on sequential pairs of characters
*
* @includeFunctionBody ../../test/StringTest.kt fold
*/
public inline fun <R> String.fold(initial: R, operation: (R, Char) -> R): R {
var answer = initial
for (c in this) answer = operation(answer, c)
return answer
}
/**
* Folds all characters from right to left with the *initial* value to perform the operation on sequential pairs of characters
*
* @includeFunctionBody ../../test/StringTest.kt foldRight
*/
public inline fun <R> String.foldRight(initial: R, operation: (Char, R) -> R): R = reverse().fold(initial, { x, y -> operation(y, x) })
/**
* Applies binary operation to all characters in a string, going from left to right.
* Similar to fold function, but uses the first character as initial value
*
* @includeFunctionBody ../../test/StringTest.kt reduce
*/
public inline fun String.reduce(operation: (Char, Char) -> Char): Char {
val iterator = this.iterator()
if (!iterator.hasNext()) {
throw UnsupportedOperationException("Empty string can't be reduced")
}
var result = iterator.next()
while (iterator.hasNext()) {
result = operation(result, iterator.next())
}
return result
}
/**
* Applies binary operation to all characters in a string, going from right to left.
* Similar to foldRight function, but uses the last character as initial value
*
* @includeFunctionBody ../../test/StringTest.kt reduceRight
*/
public inline fun String.reduceRight(operation: (Char, Char) -> Char): Char = reverse().reduce { x, y -> operation(y, x) }
/**
* Groups the characters in the string into a new [[Map]] using the supplied *toKey* function to calculate the key to group the characters by
*
* @includeFunctionBody ../../test/StringTest.kt groupBy
*/
public inline fun <K> String.groupBy(toKey: (Char) -> K): Map<K, String> = groupByTo<K>(HashMap<K, String>(), toKey)
/**
* Groups the characters in the string into the given [[Map]] using the supplied *toKey* function to calculate the key to group the characters by
*
* @includeFunctionBody ../../test/StringTest.kt groupBy
*/
public inline fun <K> String.groupByTo(result: MutableMap<K, String>, toKey: (Char) -> K): Map<K, String> {
for (c in this) {
val key = toKey(c)
val str = result.getOrElse(key) { "" }
result[key] = str + c
}
return result
}
/**
* Creates a new string from all the characters separated using the *separator* and using the given *prefix* and *postfix* if supplied.
*
* If a string could be huge you can specify a non-negative value of *limit* which will only show a substring then it will
* a special *truncated* separator (which defaults to "..."
*
* @includeFunctionBody ../../test/StringTest.kt makeString
*/
public inline fun String.makeString(separator: String = ", ", prefix: String = "", postfix: String = "", limit: Int = -1, truncated: String = "..."): String {
val buffer = StringBuilder()
appendString(buffer, separator, prefix, postfix, limit, truncated)
return buffer.toString()
}
/**
* Returns an Appendable containing the everything but the first characters that satisfy the given *predicate*
*
* @includeFunctionBody ../../test/StringTest.kt dropWhile
*/
public inline fun <T: Appendable> String.dropWhileTo(result: T, predicate: (Char) -> Boolean): T {
var start = true
for (element in this) {
if (start && predicate(element)) {
// ignore
} else {
start = false
result.append(element)
}
}
return result
}
/**
* Returns a new String containing the everything but the first characters that satisfy the given *predicate*
*
* @includeFunctionBody ../../test/StringTest.kt dropWhile
*/
public inline fun String.dropWhile(predicate: (Char) -> Boolean): String = dropWhileTo(StringBuilder(), predicate).toString()
/**
* Returns a string containing everything but the first *n* characters
*
* @includeFunctionBody ../../test/StringTest.kt drop
*/
public inline fun String.drop(n: Int): String = dropWhile(countTo(n))
/**
* Returns an Appendable containing the first characters that satisfy the given *predicate*
*
* @includeFunctionBody ../../test/StringTest.kt takeWhile
*/
public inline fun <T: Appendable> String.takeWhileTo(result: T, predicate: (Char) -> Boolean): T {
for (c in this) if (predicate(c)) result.append(c) else break
return result
}
/**
* Returns a new String containing the first characters that satisfy the given *predicate*
*
* @includeFunctionBody ../../test/StringTest.kt takeWhile
*/
public inline fun String.takeWhile(predicate: (Char) -> Boolean): String = takeWhileTo(StringBuilder(), predicate).toString()
/**
* Returns a string containing the first *n* characters
*
* @includeFunctionBody ../../test/StringTest.kt take
*/
public inline fun String.take(n: Int): String = takeWhile(countTo(n))
/** Copies all characters into the given collection */
public inline fun <C: MutableCollection<in Char>> String.toCollection(result: C): C {
for (c in this) result.add(c)
return result
}
/** Copies all characters into a [[LinkedList]] */
public inline fun String.toLinkedList(): LinkedList<Char> = toCollection(LinkedList<Char>())
/** Copies all characters into a [[List]] */
public inline fun String.toList(): List<Char> = toCollection(ArrayList<Char>(this.length()))
/** Copies all characters into a [[Collection] */
public inline fun String.toCollection(): Collection<Char> = toCollection(ArrayList<Char>(this.length()))
/** Copies all characters into a [[Set]] */
public inline fun String.toSet(): Set<Char> = toCollection(HashSet<Char>())
+164 -1
View File
@@ -1,7 +1,7 @@
package test
import java.util.Collections
import kotlin.test.*
import org.junit.Test as test
class StringTest {
@@ -49,4 +49,167 @@ class StringTest {
assertEquals("uRL", "URL".decapitalize())
}
test fun filter() {
assertEquals("acdca", "abcdcba".filter { !it.equals('b') })
assertEquals("1234", "a1b2c3d4".filter { it.isDigit() })
}
test fun filterNot() {
assertEquals("acdca", "abcdcba".filterNot { it.equals('b') })
assertEquals("abcd", "a1b2c3d4".filterNot { it.isDigit() })
}
test fun reverse() {
assertEquals("dcba", "abcd".reverse())
assertEquals("4321", "1234".reverse())
assertEquals("", "".reverse())
}
test fun forEach() {
val data = "abcd1234"
var count = 0
data.forEach{ count++ }
assertEquals(data.length(), count)
}
test fun all() {
val data = "AbCd"
assertTrue {
data.all { it.isJavaLetter() }
}
assertNot {
data.all { it.isUpperCase() }
}
}
test fun any() {
val data = "a1bc"
assertTrue {
data.any() { it.isDigit() }
}
assertNot {
data.any() { it.isUpperCase() }
}
}
test fun appendString() {
val data = "kotlin"
val sb = StringBuilder()
data.appendString(sb, "^", "<", ">")
assertEquals("<k^o^t^l^i^n>", sb.toString())
}
test fun find() {
val data = "a1b2c3"
assertEquals('1', data.find { it.isDigit() })
assertNull(data.find { it.isUpperCase() })
}
test fun findNot() {
val data = "1a2b3c"
assertEquals('a', data.findNot { it.isDigit() })
assertNull(data.findNot { it.isJavaLetterOrDigit() })
}
test fun partition() {
val data = "a1b2c3"
val pair = data.partition { it.isDigit() }
assertEquals("123", pair.first, "pair.first")
assertEquals("abc", pair.second, "pair.second")
}
test fun flatMap() {
val data = "abcd"
val result = data.flatMap { Collections.singletonList(it) }
assertEquals(data.size, result.count())
assertEquals(data.toCharList(), result)
}
test fun fold() {
// calculate number of digits in the string
val data = "a1b2c3def"
val result = data.fold(0, { digits, c -> if(c.isDigit()) digits + 1 else digits } )
assertEquals(3, result)
//simulate all method
assertEquals(true, "ABCD".fold(true, { r, c -> r && c.isUpperCase() }))
//get string back
assertEquals(data, data.fold("", { s, c -> s + c }))
}
test fun foldRight() {
// calculate number of digits in the string
val data = "a1b2c3def"
val result = data.foldRight(0, { c, digits -> if(c.isDigit()) digits + 1 else digits })
assertEquals(3, result)
//simulate all method
assertEquals(true, "ABCD".foldRight(true, { c, r -> r && c.isUpperCase() }))
//get string back
assertEquals(data, data.foldRight("", { s, c -> "" + s + c }))
}
test fun reduce() {
// get the smallest character(by char value)
assertEquals('a', "bacfd".reduce { v, c -> if (v > c) c else v })
failsWith<UnsupportedOperationException> {
"".reduce { a, b -> '\n' }
}
}
test fun reduceRight() {
// get the smallest character(by char value)
assertEquals('a', "bacfd".reduceRight { c, v -> if (v > c) c else v })
failsWith<UnsupportedOperationException> {
"".reduceRight { a, b -> '\n' }
}
}
test fun groupBy() {
// collect similar characters by their int code
val data = "ababaaabcd"
val result = data.groupBy { it.toInt() }
assertEquals(4, result.size)
assertEquals("bbb", result.get('b'.toInt()))
}
test fun makeString() {
val data = "abcd"
val result = data.makeString("_", "(", ")")
assertEquals("(a_b_c_d)", result)
val data2 = "verylongstring"
val result2 = data2.makeString("-", "[", "]", 11, "oops")
assertEquals("[v-e-r-y-l-o-n-g-s-t-r-oops]", result2)
}
test fun dropWhile() {
val data = "ab1cd2"
val result = data.dropWhile { it.isJavaLetter() }
assertEquals("1cd2", result)
}
test fun drop() {
val data = "abcd1234"
assertEquals("d1234", data.drop(3))
assertEquals(data, data.drop(-2))
assertEquals("", data.drop(data.length + 5))
}
test fun takeWhile() {
val data = "ab1cd2"
val result = data.takeWhile { it.isJavaLetter() }
assertEquals("ab", result)
}
test fun take() {
val data = "abcd1234"
assertEquals("abc", data.take(3))
assertEquals("", data.take(-7))
assertEquals(data, data.take(data.length + 42))
}
}