https://kotlinlang.org logo
Title
t

Travis Griggs

04/17/2023, 4:58 AM
I've been dorking around with the following for a while. I need a fresh perspective. I want a function that will filter out all but the first occurrence of a character in a string. E.g.
"." >>> "."
"" >>> ""
"foo" >>> "foo"
"abc.xyz.com.dat.is.cool" >>> "abc.xyzcomdatiscool"
"abc." >>> "abc."
"abc.x" >>> "abc.x"
"abc.xyz." >>> "<http://abc.xyz|abc.xyz>"
I can solve this "straightforward" style with something like this:
fun keepFirst(input:String, pariah:Char) : String {
    val builder = StringBuilder()
    val iter = input.iterator()
    while (iter.hasNext()) {
        val next = iter.next()
        builder.append(next)
        if (next == pariah) {
            break
        }
    }
    while (iter.hasNext()) {
        val next = iter.next()
        if (next != pariah) {
       	 builder.append(next)
        }
    }
    return builder.toString()
}
But it seems like I should be able to do something more general, more one line, more functional. I don't want to use a "findIndex" solution followed by a replace, that has to cope with edge cases. And I was hoping to do it in single pass. What I wanted was something like:
val stream = input.iterator()
return (stream.takeThrough { char == '.' } + stream.filter { char != '.' }).toString()
Or something like so, but alas, there is no takeThrough analog.
e

Edwar D Day

04/17/2023, 6:32 AM
Something like this might work. It's at least shorter but only supports a dot
fun main() {
    val input = "abc.xyz."
    val regex = "([^\\.]*)\\.?".toRegex()
    val result = regex.findAll(input).withIndex().joinToString(separator = "") { (index, match) ->
        if (index == 0) match.groupValues[0] else match.groupValues[1]
    }
    println(result)
}
y

Youssef Shoaib [MOD]

04/17/2023, 6:42 AM
This is very stupid, but:
fun String.keepFirst(char: Char): String = takeWhile { it != char } + char + dropWhile { it != char }.replace("$char", "")
e

ephemient

04/17/2023, 8:43 AM
only if you're guaranteed that the char exists, though
this will handle that as well as use built-in extensions instead:
fun String.keepFirst(c: Char): String =
    if (c in this) {
        substringBefore(c) + c + substringAfter(c).replace(c.toString(), "")
    } else {
        this
    }
but the most direct way of expressing what you want is
fun String.keepFirst(c: Char): String {
    var isFirst = true
    return filter { it != c || isFirst.also { isFirst = false } }
}
btw your original function could be simplified with
fun keepFirst(input: String, pariah: Char): String = buildString {
    val iterator = input.iterator()
    for (next in iterator) {
        append(next)
        if (next == pariah) break
    }
    for (next in iterator) {
        if (next != pariah) append(next)
    }
}
t

Travis Griggs

04/17/2023, 5:31 PM
The solution I wanted was something like your "direct" solution. I tried mutliple variants of the "hasFound" latched state, and they were all a pain, because you had defer use to the next iteration. I need to reach for the also/run/with functions easier. also is just the magic needed here. thanks