I've been dorking around with the following for a ...
# getting-started
t
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.
Copy code
"." >>> "."
"" >>> ""
"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:
Copy code
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:
Copy code
val stream = input.iterator()
return (stream.takeThrough { char == '.' } + stream.filter { char != '.' }).toString()
Or something like so, but alas, there is no takeThrough analog.
e
Something like this might work. It's at least shorter but only supports a dot
Copy code
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
This is very stupid, but:
Copy code
fun String.keepFirst(char: Char): String = takeWhile { it != char } + char + dropWhile { it != char }.replace("$char", "")
e
only if you're guaranteed that the char exists, though
this will handle that as well as use built-in extensions instead:
Copy code
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
Copy code
fun String.keepFirst(c: Char): String {
    var isFirst = true
    return filter { it != c || isFirst.also { isFirst = false } }
}
🎉 1
btw your original function could be simplified with
Copy code
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
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