Разница между созданием словаря путем сокращения массива по сравнению с назначением каждого элемента на итерации - PullRequest
1 голос
/ 24 октября 2019

Я столкнулся с вопросом о StackOverflow: Swift - Преобразование массива в словарь , где пользователь хочет взять элементы массива и хочет поместить их в словарь и присвоить 0 каждому из них. (Введите в качестве игроков и значение в качестве их очков) Итак:

var playerNames = ["Harry", "Ron", "Hermione"]

становится

var scoreBoard: [String:Int] = [ "Ron":0, "Harry":0, "Hermione":0 ]

На этот вопрос было получено 2 ответа: 1) Использует уменьшение в массиве

let scoreboard = playerNames.reduce(into: [String: Int]()) { $0[$1] = 0 }

2) Создает словарь и выполняет итерацию в массиве, чтобы добавить к нему каждую пару ключей значения

var dictionary = [String: Int]()
for player in playerNames {
    dictionary[player] = 0
}

Я взял функцию BenchTimer из https://github.com/nyisztor/swift-algorithms, чтобы проверить оба эти способа решения. И они оба, кажется, работают в O (n).

enter image description here

enter image description here

Я былИнтересно, почему мы предпочли бы первое по сравнению с другим, так как тот, кто написал второе решение, получил плохой комментарий о своих навыках кодирования.

Редактировать: Apple в новых версиях отказывается от некоторых функций, не так ли? лучше придерживаться основ и создавать свои собственные способы делать вещи?

Спасибо за ответы

Ответы [ 3 ]

2 голосов
/ 25 октября 2019

Сегодня, IMO, вы не должны использовать ни один из них. Теперь у нас есть Dictionary.init(uniqueKeysWithValues:) и .init(_:uniquingKeysWith:), которые намного более четко указывают их намерение и делают явные угловые случаи, такие как дубликаты ключей.

Если вы статически можете доказать, что все ключи уникальны, то выбудет использовать первый:

let scoreboard = Dictionary(uniqueKeysWithValues: playerNames.map { (name: $0, score: 0) })

Если вы не можете доказать, что ключи уникальны, вы должны использовать второй, который позволит вам явно решить, что делать в случае конфликта.

let scoreboard = Dictionary(playerNames.map { (name: $0, score: 0) },
                            uniquingKeysWith: { first, _ in first })

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

1 голос
/ 25 октября 2019

так не лучше ли придерживаться основ и создавать свои собственные способы ведения дел?

Я так не думаю. Сообщество Swift определенно не относится к этому мышлению. Swift отдает приоритет созданию осмысленных абстракций и упрощений, при условии, что они ценны и постепенно раскрываются.

Сообщество Go разделяет ваше мышление, но это довольно болезненно (IMO). Стандартная библиотека Go даже не имеет API для обращения строки. Вы должны приготовить это сами. И это сложнее, чем думает большинство людей. Если вы думаете, что это просто вопрос создания цикла для обращения байтов, нет, он полностью сломан для Unicode (но, вероятно, останется незамеченным для простых тестовых примеров ASCII, таких как "hello" или что-то еще).

Аналогично,если вы пишете циклы for каждый раз, когда хотите реализовать map, вы можете забыть вызвать Array.reserveCapacity(_:). Забывание этого приведет к множественному распределению массива и превратит ваш алгоритм O(n) в настоящий момент в O(n^2). Подобные «ошибки» могут дать небольшую производительность или корректность, поэтому есть большая выгода от использования популярных, совместно используемых реализаций этих вещей.

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

О двух фрагментах кода

Я бы не стал использовать ни один из них

Первый подход:

  1. Использует функцию (reduce), но не подходит для работы (Dictionary.init(uniqueKeysWithValues:)). См. https://github.com/amomchilov/Blog/blob/master/Don't%20abuse%20reduce.md
  2. Забывает зарезервировать емкость в словаре, поэтому он вызывает несколько операций изменения размера (которые включают перераспределение памяти и перефразирование всех ключей в словаре)

Второйподход:

  1. Оставляет словарь излишне изменчивым
  2. Также забывает резервировать емкость.

Вместо этого я бы порекомендовал Роб Нейпир или подход Мартина Р. . Оба они более выразительны вашего намерения. Роб также использует последовательность с известным размером, которая позволяет Dictionary.init(uniqueKeysWithValues:) внутренне выделить достаточно памяти для словаря, так как это будет необходимо для соответствия всем значениям.

0 голосов
/ 24 октября 2019

Вообще говоря, вы должны предпочесть функциональную идиому (т.е. уменьшить) циклу for по нескольким причинам:

  1. Функциональная версия передает намерение. В этом случае мы уменьшаем массив до словаря - не лучший пример, но обычно с помощью редукции преобразовываем вектор в один скаляр с помощью некоторой функции объединения.
  2. Функциональная версия верна по умолчанию, посколькуэто было проверено. Это может показаться тривиальным в случае снижения, но это гораздо меньше, если вы посмотрите на что-то вроде shuffled. Как легко вы можете посмотреть на цикл for и сказать мне, выполняет ли он случайное перемешивание Фишера-Йетса и правильно ли он реализован? Точно так же конвейер функций гораздо проще читать, чем последовательность последовательных циклов for или единичный цикл for, который выполняет 5 разных действий.

  3. Функциональная версия часто (но не в этомcase) неизменяемые и неизменяемые значения легче рассуждать, чем изменяемое состояние, поскольку они никогда не изменяются.

  4. Функциональные методы в Swift.Sequence в основном имеют аналогичные методы в Combine.Publisher. Это означает, что вы можете использовать один набор функциональных идиом во всех последовательностях, независимо от того, являются ли они синхронными или асинхронными / реактивными.

...