Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to construct a HashSet with initializator function in Kotlin?

Tags:

kotlin

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?

like image 447
Vilmantas Baranauskas Avatar asked Jan 12 '16 08:01

Vilmantas Baranauskas


3 Answers

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)
}
like image 63
Kirill Rakhman Avatar answered Oct 12 '22 00:10

Kirill Rakhman


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
}
like image 32
miensol Avatar answered Oct 11 '22 22:10

miensol


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()
}
like image 35
mfulton26 Avatar answered Oct 11 '22 22:10

mfulton26