2016-01-12 2 views
5

Чтобы прочитать Звезды из файла в Facebook Hacker Cup's 2016 проблемы Бумеранг Constelations следующая функция расширения может быть определена:Есть ли способ построить HashSet с функцией инициализации в Котлине?

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

код компактен, но значения первого чтения в массив, а затем преобразуется в HashSet. Есть ли способ напрямую инициализировать HashSet с размером n и функцией инициализации в Котлине?

UPDATE: Есть ли существующего пути в стандартных библиотеках Котлина?

+0

Похоже, вы задаете XY вопрос (http://xyproblem.info/). Вы скорее спросите, как наиболее эффективно использовать свою функцию 'readStars'? –

+0

Я ответил, что ваш вопрос представляется (X) вместо 'HashSet' (Y), см. Новый ответ ниже. –

+0

Вы пытались создать «HashSet» самостоятельно? Как "val mySet = HashSet (...)'? Если это так, вы уже знаете, как создать 'HashSet'. Вы не показали, что вы уже пробовали в своем вопросе, так что это сбивает с толку, потому что любой может создать «HashSet», если они захотят. Без Kotlin stdlib. stdlib добавит только вспомогательную функцию, чтобы сделать ее более согласованной с стилем Kotlin, но она не добавляет 'HashSet' к доступным параметрам. –

ответ

5

С HashSet является классом Java, поэтому вы можете инициализировать его только способом, предоставленным JDK.

Хотя есть нет вспомогательный метод в Котлин выполнения легко, чтобы написать его самостоятельно, как так:

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

Вы даже можете назвать свою функцию «HashSet», чтобы она выглядела как обычный конструктор HashSet. Я хотел знать, есть ли что-то, что уже присутствует в стандартных kotlin libs, или, может быть, совсем другой подход. –

+1

@ VilmantasBaranauskas не рекомендуется скрывать конструктор с функцией расширения. –

+1

@miensol Ваш код не должен предполагать, что начальная емкость HashSet будет такой же, как и загруженные элементы. В большинстве случаев они не были бы оптимальными. Кроме того, ваша реализация не используется из метода 'readStars' (как бы вы интегрировали это без каких-либо значений ранее, что уже является проблемой) –

7

Вы всегда можете использовать apply для инициализации объектов в месте:

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

Если это слишком неудобно, каждый раз набирайте шрифт, записывайте функцию расширения:

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

Использование:

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

Как @miensol указал HashSet инициализации ограничивается конструкторами доступны в JDK. Kotlin добавил функцию hashSetOf, которая инициализирует пустой HashSet, а затем добавляет к нему указанные элементы.

Чтобы избежать первым чтения значений в массив можно использовать kotlin.Sequence кто «значения оцениваются ленивым»:

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

Хороший ответ. Ваш 'splitToSequence()', который затем выполняет 'take (2)' и делает 'toList()' на самом деле ничего не сохраняет в конце. Результаты в той же работе и требуют выделения последовательности, итератора для последовательности, класса Take из последовательности, а затем списка. Так что в конечном итоге это больше средств. Версия, которая использует итератор, лучше, но затем она выделяет класс как Iterator, поэтому не обязательно ничего ничего не сохраняет. Вы просто меняете то, что выделяется, и, возможно, создавайте больше. –

+0

@JaysonMinard Возможно, вы правы. Я не сравнивал накладные расходы между Kotlin Sequence и списками, но из того, что я могу сказать в первом решении, 'split' создает список, а затем' map' создает другой список. Во втором решении «splitToSequence» создает лениво оцененный «итератор», а затем список (так что * если * последовательность имеет меньше накладных расходов, а затем список, то это победа). В решениях 'iterator' ни один из списков не создается (так что снова * если * последовательность имеет меньше накладных расходов, а затем список, то это еще один выигрыш). Я полагаю, что для небольших списков вы абсолютно правы: используйте список. Я обновлю. Спасибо. – mfulton26

+1

Кэш-память процессора сделает копию списков очень дешевой. Lazy не всегда дешевле для мелочей.'split (char)' делает последовательность, выделяет анонимный 'Iterable', который затем является' map' в 'List'. 'splitToSequence (char)' делает последовательность, а затем, когда вы 'map' захватывает экземпляр' TransformingSequence', который затем создает анонимный экземпляр 'Iterator', который затем является' toList'. Таким образом, вы делаете все, что делает другая версия +1 другим распределением. И действительно, мелочи лучше, чем скопированные, действительно большие вещи, может быть, и нет. Зависит от того, насколько близка по времени копия, кэш процессора, ... –

1

Похоже, вы задаете вопрос XY (http://xyproblem.info/). Вы действительно хотите знать, как написать readStars наиболее эффективным способом, но вместо этого вы спрашиваете о HashSet. Я думаю, что @ mfulton26 также отвечает на ваш вопрос в зависимости от того, что задается.

Вот ответ на «как я пишу это наиболее эффективным образом:»

У вас есть два варианта. Во-первых, версия, которая автоматически закрывает поток в конце:

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

И во-вторых, версия, что это не так:

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

Ни версия создает массив, и не они делают копии данных. Они передают данные через последовательность, которая создает набор и заполняет его напрямую.

Другие ноты

Нет необходимости использовать раскол, если вы действительно обеспокоены распределения и производительности. Просто используйте indexOf(char) и разделите строку самостоятельно, используя substring.

Если вы раскол, то, пожалуйста, используйте split(char) не split(String) когда вы хотите разделить на char

+2

эти две последние символы предназначены для нескважинных. Я предлагаю изменить их на ссылку на документы и smily – voddan

+0

Я удалил последнюю часть HashSet, потому что я уже проголосовал за вопрос о том, как создать HashSet, что является плохим вопросом для SO. Не нужно повторять это здесь в ответе. –

+0

«Вызов подстроки не копирует внутренний массив, а разделяет его и очень эффективен». - Это уже не так, начиная с Java 7 – Ilya

Смежные вопросы