2016-01-12 4 views
5

Per leggere Stelle da un file nel Boomerang Constelations problema Facebook Hacker Cup's 2016, seguente funzione di estensione può essere definita:C'è un modo per costruire un HashSet con la funzione initializator in Kotlin?

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

codice è compatto ma i valori sono leggere prima in una matrice e poi convertito HashSet. C'è un modo per inizializzare direttamente un HashSet con le dimensioni di n e la funzione initializator in Kotlin?

UPDATE: C'è un esistente modo in librerie standard di Kotlin?

+0

Sembra che ti stai chiedendo una domanda XY (http://xyproblem.info/). Preferiresti chiedere come eseguire 'readStars' nel modo più efficiente? –

+0

Ho risposto a quale domanda sembra essere (X) invece di 'HashSet' (Y), vedere la nuova risposta di seguito. –

+0

Hai provato a creare un 'HashSet' tu stesso? Come "val mySet = HashSet (...)'? Se è così, sapresti già come creare un 'HashSet'. Non hai mostrato ciò che hai già provato nella tua domanda, quindi è confuso perché chiunque può creare un 'HashSet' se lo desiderano Senza Kotlin stdlib Lo stdlib aggiungerebbe solo una funzione helper per renderlo più coerente con lo stile di Kotlin, ma non aggiunge' HashSet' alle opzioni disponibili –

risposta

5

Dal HashSet è una classe java in modo che è possibile solo inizializzarlo in un modo fornito da JDK.

Mentre non c'è alcun metodo di supporto in Kotlin runtime è facile da scrivere da soli in questo modo:

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 
} 
+0

Puoi anche nominare la tua funzione "HashSet" in modo che assomigli ad un normale costruttore di HashSet. Volevo sapere se c'è qualcosa di già presente nelle librerie standard di Kotlin, o forse un approccio completamente diverso. –

+1

@VilmantasBaranauskas non è consigliabile provare e nascondere un costruttore con una funzione di estensione. –

+1

@miensol il tuo codice non dovrebbe presumere che la capacità iniziale di HashSet sarà la stessa degli articoli caricati. La maggior parte delle volte non sarebbero per prestazioni ottimali. Inoltre la tua implementazione non è utilizzabile dal metodo 'readStars' (come lo integreresti senza tenere i valori in qualcosa, che è già il problema) –

7

si può sempre usare apply per inizializzare gli oggetti in-place:

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

Se anche questo è troppo fastidioso digitare ogni volta, scrivere una funzione di estensione:

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

utilizzati:

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

Come @miensol ha sottolineato HashSet inizializzazione è limitata ai costruttori messi a disposizione dal JDK. Kotlin ha aggiunto una funzione hashSetOf che inizializza uno HashSet vuoto e quindi aggiunge gli elementi specificati ad esso.

Per evitare prima lettura i valori in un array è possibile utilizzare un kotlin.Sequence che è "I valori sono valutati pigramente":

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

Buona risposta. Il tuo 'splitToSequence()' che quindi esegue 'take (2)' e fa 'toList()' in realtà non salva nulla alla fine. Risultati nello stesso lavoro e richiede l'assegnazione di una sequenza, Iterator per la sequenza, una classe Take dalla sequenza e quindi una lista. Quindi questo è più allocazioni alla fine. La versione che utilizza un iteratore è migliore, ma poi assegna una classe come Iterator, quindi non salva necessariamente di nuovo nulla. Basta cambiare ciò che è allocato e magari crearne di più. –

+0

@JaysonMinard Potresti avere ragione. Non ho confrontato il sovraccarico tra una sequenza e liste di Kotlin, ma da quello che posso dire nella prima soluzione 'split' crea una lista e poi' map' crea un'altra lista. Nella seconda soluzione 'splitToSequence' crea un' iteratore 'valutato pigramente e quindi una lista (quindi * se * una sequenza ha meno overhead di una lista, allora questa è una vittoria). Nelle soluzioni 'iterator' non viene mai creata alcuna lista (quindi ancora * se * una sequenza ha meno overhead di una lista, allora questa è un'altra vittoria). Suppongo che per le piccole liste hai assolutamente ragione: usa una lista. Aggiornerò Grazie. – mfulton26

+1

La cache della CPU renderà la copia degli elenchi molto economica. Pigro non è sempre più economico per le piccole cose.'split (char)' crea una sequenza, assegna un 'Iterable' anonimo che è quindi' map' in un 'List'. 'splitToSequence (char)' crea una sequenza, e quindi quando 'map' esegue il puntamento di un'istanza di' TransformingSequence' che quindi crea un'istanza anonima di 'Iterator' che è quindi' toList'. Quindi fai tutto ciò che l'altra versione fa +1 all'altra allocazione. E davvero, le piccole cose sono migliori come copiate, cose veramente grandi forse no. Dipende da quanto è vicina la copia, la cache della CPU, ... –

1

Sembra che ti stai chiedendo una domanda XY (http://xyproblem.info/). Vuoi davvero sapere come scrivere readStars nel modo più efficiente, ma invece chiedi informazioni su HashSet. Penso che @ mfulton26 risponda alla tua domanda a seconda di cosa viene chiesto.

Ecco la risposta per "come faccio a scrivere questo nel modo più efficiente:"

si hanno due opzioni. In primo luogo, una versione che auto-chiude il flusso alla fine:

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() 
    } 
} 

E in secondo luogo, una versione che non è così:

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() 
} 

Né versione crea una matrice, non fanno a fare copie di dati. Trasmettono i dati attraverso una sequenza che crea il Set e lo riempie direttamente.

Altre note

Non c'è bisogno di utilizzare scissione se siete veramente preoccupati per le allocazioni e le prestazioni. Basta usare indexOf(char) e dividere la stringa da soli usando substring.

Se fate una spaccatura, quindi si prega di utilizzare split(char) non split(String) quando si sta cercando di dividere su una char

+2

questi due ultimi charapters una specie di non richiesto. Suggerisco di cambiarli in un link ai documenti e in una smily – voddan

+0

Ho eliminato l'ultima parte di HashSet perché ho già votato la domanda in cui si chiede come creare un HashSet, che è una cattiva domanda per SO. Non c'è bisogno di ripeterlo qui nella risposta. –

+0

"Una chiamata alla sottostringa non copia la matrice interna, ma la condivide ed è molto efficiente." - Non è più così a partire da Java 7 – Ilya

Problemi correlati