Does kotlin have a specific function to convert fr...
# announcements
a
Does kotlin have a specific function to convert from
List<Char>
to
String
or should we just use
joinToString
? I’m trying to shuffle letters in a word and I’m doing so by
String.toList().shuffled()
a
If you don't want the delimiter (separator between two chars), you can simply:
StringBuilder(list.size).apply { list.forEach { append(it) } }.toString()
Basically
joinToString("")
does the same, but has a lot more checks to append more elements like limit, prefix, postfix, separator, and value transformer. ^ this will just remove all those unnecessary check made during concatenation with joinToString(""). Also pre-specifying length will pre-allocate size of underlying array so no resizing is required thereafter.
n
You can potentially handle preallocation in join to, Kotlin methods often attempt to query iterables for their size
String builders also most likely need to make a deep copy at the end, to ensure the resulting string is immutable
That's two full copies, from the sequence into the builder and then from the builder into the string
a
List<>.joinToString() creates StringBuilder of default buffer.
n
Join probably only needs one
You're saying that joinToString is implemented using stringBuilder?
a
Yes
n
Ah that's surprisingly lazy :-)
a
Copy code
public fun <T> Iterable<T>.joinToString(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((T) -> CharSequence)? = null): String {
    return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString()
}
And joinTo as:
Copy code
public fun <T, A : Appendable> Iterable<T>.joinTo(buffer: A, separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "", limit: Int = -1, truncated: CharSequence = "...", transform: ((T) -> CharSequence)? = null): A {
    buffer.append(prefix)
    var count = 0
    for (element in this) {
        if (++count > 1) buffer.append(separator)
        if (limit < 0 || count <= limit) {
            buffer.appendElement(element, transform)
        } else break
    }
    if (limit >= 0 && count > limit) buffer.append(truncated)
    buffer.append(postfix)
    return buffer
}
n
That means it does two copies
When it should only need to do one
a
How do you do single copy?
n
I don't know how it's done in terms of the API that Java makes publicly available for string
But algorithmically you definitely only need one copy
It's pretty obvious. The only reason that you need the second copy is defensive, because string is immutable and builder is mutable, so they can't point at the same data
But join owns the only reference to the builder and doesn't leak it, so that defensive copy is unnecessary
a
Even if you directly send a CharArray, java's String implementation will still going to do a copy of it, to make it immutable
n
Right, so basically I guess Java doesn't expose a non defensive copying constructor, so it's not possible for that reason
Seems like a significant oversight
a
StringBuilders won't be very inefficient, single copy won't hurt much. This is not C/C++ 😁😁.
sb will soon be garbage collected, dereferenced by creating the string
n
well, it's less inefficient in relative terms, because other things are more inefficient ;-)
Java can't magically speed up a copy, copying is copying
But it shows the shortcoming of working with objects instead of functions when you have immutable types
String should have an associated factory function, or set of factory functions, with access to the internals
I've dealt with this myself, when I implemented a simple ImmutableList, I implemented my own map, filter, etc with private access as it makes things much more efficient
n
thoughts?
you can go straight from string to char[] at the beginning, but there's no easy
shuffle
method for arrays, apparently
seems like premature optimization to worry about how many copies are happening, but it's kinda fun 😛
a
If you really want that much optimization, you can simply make your own swapping function:
Copy code
fun CharArray.shuffle(): CharArray {
    for (i in indices) {
        swap(i, Random.nextInt(size))
    }

    return this
}

private fun CharArray.swap(from: Int, to: Int) {
    val tmp = get(from)
    set(from, get(to))
    set(to, tmp)
}
Then use the primitive CharArray:
Copy code
val shuffledString = String("Hello World".toCharArray().shuffle())
n
I mean if you don't want to worry about optimization, joinToString has a much nicer API for this case (and most cases) compared to string builder
But yeah implementing a shuffle extension method from string to string (using char arrays in the middle as above) seems nicest
Make the use site code more readable anyway
n
String.toList().shuffled().joinToString(separator = "")
is probably fine, and if you want a tiny bit more efficiency you can pass in a pre-sized StringBuilder (which has all already been discussed already). were it up to me I'd probably do one of those. maybe if it's a large string (>100 chars) I'd use the pre-sized StringBuilder.
n
Yeah most likely that's fine, I agree, and it's the least work.
f
Even with
char[]
you'll have an additional copy the moment you want to go back to
String
because Java checks if it's Unicode. There are APIs that can do it without but they're all package scoped in the JDK and go through multiple hoops to keep them hidden. The UUID implementation is using them and that's why I was unable to beat it's formatting speed in Kotlin.