Is there a way to construct a HashSet with initial

2019-04-29 13:59发布

问题:

To read Stars from a file in the Facebook Hacker Cup's 2016 Boomerang Constelations problem, following extension function can be defined:

fun BufferedReader.readStars(n: Int): Set<Star> {
    return Array(n) {
        val (l1, l2) = readLine().split(" ").map { it.toInt() }
        Star(l1, l2)
    }.toHashSet()
}

Code is compact but the values are first read into an array and then converted to a HashSet. Is there a way to directly initialize a HashSet with the size of n and initializator function in Kotlin?

UPDATE: Is there an existing way in standard Kotlin libs?

回答1:

You can always use apply to initialize objects in-place:

HashSet<Star>(n).apply {
    repeat(n) {
        val (l1, l2) = readLine()!!.split(' ').map { it.toInt() }
        put(Star(l1, l2))
    }
}

If that's too inconvenient too type every time, write an extension function:

inline fun <T> createHashSet(n : Int, crossinline fn: (Int) -> T) = HashSet<T>(n).apply {
    repeat(n) { add(fn(it)) }
}

Usage:

createHashSet<Star>(n) {
    val (l1, l2) = readLine()!!.split(' ').map { it.toInt() }
    Star(l1, l2)
}


回答2:

Since HashSet is a java class so you can only initialize it in a way provided by JDK.

While there's no helper method in Kotlin runtime it's easy to write it yourself like so:

public fun <T> hashSetOf(size: Int, initializer: (Int) -> T): HashSet<T> {
    val result = HashSet<T>(size)
    0.rangeTo(size - 1).forEach {
        result.add(initializer(it))
    }
    return result
}


回答3:

As @miensol has pointed out HashSet initialization is limited to the constructors made available by the JDK. Kotlin has added a hashSetOf function which initializes an empty HashSet and then adds the specified elements to it.

To avoid first reading the values into an array you can use a kotlin.Sequence who's "values are evaluated lazily":

fun BufferedReader.readStars(n: Int): Set<Star> {
    return lineSequence().take(n).map {
        val (l1, l2) = it.split(" ").map { it.toInt() }
        Star(l1, l2)
    }.toHashSet()
}


回答4:

It seems like you are asking an XY question (http://xyproblem.info/). You really want to know how to write readStars in the most efficient way, but instead you ask about HashSet. I think @mfulton26 answers your question as well depending on what is being asked.

Here is the answer for "how do I write this in the most efficient way:"

You have two options. First, a version that auto-closes the stream at the end:

fun BufferedReader.readStars(n: Int): Set<Star> {
    return use {
        lineSequence().map { line ->
            val idx = line.indexOf(' ')
            Star(line.substring(0, idx).toInt(), line.substring(idx + 1).toInt())
        }.toSet()
    }
}

And second, a version that does not:

fun BufferedReader.readStars(n: Int): Set<Star> {
    return lineSequence().map { line ->
            val idx = line.indexOf(' ')
            Star(line.substring(0, idx).toInt(), line.substring(idx+1).toInt())
        }.toSet()
}

Neither version creates an array, neither do they make copies of data. They stream the data through a sequence which creates the Set and fills it directly.

Other notes

No need to use split if you are really concerned about allocations and performance. Just use indexOf(char) and split the string yourself using substring.

If you do a split, then please use split(char) not split(String) when you are looking to split on a char



标签: kotlin