Словари (Dictionary)
Словарь - это еще один тип коллекций в программировании на Swift. С помощью словаря можно хранить данные в формате ключ-значение.
Словарь представляет собой контейнер, который хранит несколько значений одного и того же типа. Каждое значение связано с уникальным ключом, который выступает в качестве идентификатора этого значения внутри словаря. Словарь отличается от массива тем, что элементы в словаре не имеют определенного порядка. Необходимо использовать словарь, когда необходимо искать значения на основе их идентификатора, так же как в реальном мире словарь используется для поиска определения конкретного слова.
Словарь - это коллекция неуникальных неупорядоченных однотипных данных в формате “ключ-значения”. При этом ключи в этой коллекции - уникальные и хэшируемые.
Словарь в Swift можно представить как обычный орфографический словарь, в котором, каждое определяемое слово уникально (Ключ) и имеет определение (Значение).
Что из себя представляет словарь?
Словарь — это тип хеш-таблицы, обеспечивающий быстрый доступ к содержащимся в ней записям. Каждая запись в таблице идентифицируется с помощью своего ключа, который является хешируемым типом, таким как строка или число. Этот ключ используется для получения соответствующего значения, которое может быть любым объектом. В других языках подобные типы данных известны как хэши или ассоциированные массивы.
В квадратных скобках помещаются элементы словаря - литералы словаря. Литерал словаря — это список пар ключ-значение, разделенных запятыми, в котором двоеточие отделяет каждый ключ от связанного с ним значения, заключенного в квадратные скобки. Можно назначить литерал словаря переменной или константе или передать его функции, которая ожидает словарь.
Каждый ключ в словаре уникален. В качестве значения “ключа” могут быть любые типы данных, которые соответствуют протоколу Hashable. Это необходимо для того, чтоб ключи словаря гарантировано были уникальными и неповторяющимися. В качестве значения могут быть любые типы данных.
Все ключи словаря должны быть единого типа данных. То же относится и к значениям.
Если в качестве значений пары “ключ-значение” необходимо указать данные разных типов, тогда тип данных для значений в словаре будет по типу “Any”.
Нельзя изменить тип словаря после того, как он был объявлен.
Где используют словари?
Можно использовать словарь для хранения коллекции элементов в формате “ключ-значение”. Словарь похож на массив в том смысле, что оба они содержат элементы только одного типа.
Пример:
let scores = ["Bob": 42, "Alice": 10, "Daisy": 33]
print(scores)
// Output
// ["Alice": 10, "Bob": 42, "Daisy": 33]
Тип словаря scores [String: Int]
. Это обозначает, что тип ключей в словаре и тип его значений являются “String” и “Int” соответственно. В приведенном выше примере тип словаря явно не указан, потому что Swift может вывести его из контекста.
Пример структуры данных формата JSON, который широко используется различными API и веб-сервисами:
[
{
"username": "@reinder42",
"profile_url": "https://twitter.com/profiles/reinder42.jpg"
"tweets": [...]
}, {
"username": "@arthurdent",
"profile_url": "https://twitter.com/profiles/arthur_dent.jpg"
"tweets": [...]
}, {
"username": "@xxx_darthvader1999",
"profile_url": "https://twitter.com/profiles/darthvader.jpg"
"tweets": [...]
}
]
Можно увидеть, что элемент верхнего уровня — это массив, обозначенный как [...]
. И элементы в массиве являются словарями, обозначенными {...}
. Они связывают ключи со значениями, такими как “username” и “@reinder42”.
Можно преобразовать JSON в массив словарей Swift:
let twitter = [
[
"username": "@reinder42",
"profile_url": "https://twitter.com/profiles/reinder42.jpg"
"tweets": [···]
],
···
]
При переборе массива “twitter” с помощью цикла “for-in” можно получить имя пользователя Twitter:
for item in twitter {
let username = item["username"]
// Выполнить код.
}
Также словари в Swift используются:
- При сохранении пользовательских настроек по умолчанию в UserDefaults;
- При сохранение значений ключей в iCloud;
- Объекты Firebase Firestore являются словарями;
- Основой файлов в формате Plist является словарь, закодированный как XML.
Создание словаря
Если создать словарь и присвоить его переменной, то созданная коллекция будет изменяемой. Это обозначает, что можно изменить коллекцию после ее создания путем добавления, удаления или изменения элементов этой коллекции. И наоборот, когда вы присвоите словарь константе, то словарь будет неизменяемым, а его размер и содержимое не может быть изменено.
Тип словаря в Swift в полной форме пишется как Dictionary<Key, Value>, где Key - это тип значения который используется как ключ словаря, а Value - это тип значения который словарь может хранить для этих ключей.
При этом следует учитывать, что тип данных ключей словаря Key должен подчиняться протоколу Hashable, также как и тип данных значений множества.
В сокращенной форме тип словаря можно написать как [Key: Value].
Хотя две формы функционально идентичны, краткая форма является предпочтительной и используется чаще.
Декларирование словаря:
var dict1: Dictionary<String, Int>
Или так:
var dict2: [String: Int]
После объявления словаря для дальнейшей работы необходимо инициализировать словарь:
dict1 = ["Adam": 20]
dict2 = ["Ben": 22, "Charlie": 25]
Общая форма объявления и инициализации словаря:
var dict3: Dictionary<String, Int> = ["Adam": 22, "Ben": 25]
var dict4: [String: Int] = ["Adam": 22, "Ben": 25]
Объявление и инициализация словаря без указания типа данных (Создание словаря с литералом словаря):
var dict5 = Dictionary<Int, String>(dictionaryLiteral: (10, "Gin"), (20, "Henry"), (30, "Isaak"))
var dict6 = [Int: String](dictionaryLiteral: (1, "John"), (2, "Kim"), (3, "Lasly"))
Объявление и инициализация словаря без указания типа данных (Создание словаря с литералом словаря):
var dict7 = [2: "Mary", 3: "Nansy", 6: "Olaph"]
Тип коллекции будет определен исходя из типа данных элементов, с которыми коллекция инициализируется. Такая сокращенная форма используется чаще всего.
Объявление пустого словаря:
let dict8 = [Int: String]()
print(dict8)
// Output
// [:]
Еще один способ объявления пустого словаря:
let dict9: [Int: String] = [:]
print(dict9)
// Output
// [:]
Просто присвоить пустой словарь без указания типа - нельзя, так как будет ошибка: нельзя определить тип данных элементов словаря.
Работа со словарем
Можно получить доступ к словарю и изменять его либо через его методы и свойства, либо используя синтаксис индексов.
Работа с элементами словаря: добавление, получение, изменение и удаление элементов словаря
Расположение элементов в словаре неупорядочено. То есть, пары “ключ-значение” в словаре не имеют порядка по умолчанию или какой-либо сортировки.
Для примера работы со словарем создаем словарь “scores”:
var scores = ["Bob": 42, "Alice": 10, "Daisy": 33]
Добавление элемента словаря
Для добавления нового элемента (пары “ключ-значение”) в словарь, необходимо обратится к словарю и в квадратных скобках указать “ключ”, и присвоить ему значение:
scores["Finn"] = 99
Приведенный выше код добавляет новый ключ "Finn"
в словарь scores
со значением 99
.
Тип ключа и значения должны соответствовать типу данных словаря. Для словаря scores
типы данных для ключей - “String” и для значений - “Int”.
Удаление элементов словаря
Удаление элемента из словаря выполняется путем присвоения ему значения “nil”:
scores["Alice"] = nil
Кроме того, можно удалить пару “ключ-значение” из словаря с помощью метода removeValue(forKey:)
. Этот метод удаляет пару “ключ-значение”, если она существует, и затем - возвращает значение, либо возвращает “nil”, если значения не существует:
if let removedValue = scores.removeValue(forKey: "Alice") {
print("The removed score value is \(removedValue).")
} else {
print("The dictionary does not contain a value for Alice.")
}
// Output
// Выведет "The removed score value is 101."
Метод removeValue(forKey:Key)
удаляет указанный ключ и связанное с ним значение из словаря. При этом он также возвращает удаленное значение (опционального типа), если оно существует или nil - если его нет:
var hues = ["Heliotrope": 296, "Coral": 16, "Aquamarine": 156]
if let value = hues.removeValue(forKey: "Coral") {
print("The value \(value) was removed.")
}
// Output
// The value 16 was removed.
В этом примере, функция removeValue(forKey: "Coral")
удалила элемент 16
с ключом "Coral"
и вернула его значение, которое было использовано для вывода в консоль.
Получение и изменение элементов словаря
Для получения значения из словаря необходимо обратиться к словарю и в квадратных скобках указать необходимый ключ:
let score = scores["Bob"]
При этом тип полученного значения из словаря будет опциональным. Почему это происходит? Потому что, нельзя быть уверенным, что элемент с данным ключом существует в словаре. В случае, когда используется ключ, которого нет в словаре, возвращается значение nil.
В примере, для словаря score
значение будет с типом Int? (опциональный тип Int):
var scores = [
"Bob": 42,
"Alice": 10,
"Daisy": 33
]
print(scores)
// Output
// ["Daisy": 33, "Bob": 42, "Alice": 10]
let score_bob = scores["Bob"]
print(score_bob)
// Output
// Optional(42)
let score_unknown = scores["Arthur"]
print(score_unknown)
// Output
// nil
Значение score_bob
имеет тип Optional(42)
или Int?
. Значение score_unknown
- nil
.
Можно использовать опциональное связывание при получении значений из словаря:
if let score = scores["Bob"] {
print(score)
}
Также можно изменить словарь с помощью похожего синтаксиса:
var scores = ["Bob": 42, "Alice": 10, "Daisy": 33]
scores["Alice"] = 101
print(scores)
// Output
// ["Alice": 101, "Bob": 42, "Daisy": 33]
Значение для ключа "Alice"
теперь изменено на 101
. Синтаксис для добавления, удаления, изменения и получения элементов из словаря по сути одинаков.
Но что, если необходимо узнать, была ли пара ключ-значение добавлена в словарь или изменена по сравнению с существующей парой ключ-значение? Здесь поможет функция (метод словаря) updateValue(_:forKey:)
.
Данный метод можно использовать в качестве альтернативы индексам, чтобы установить или обновить значение для определенного ключа. Как и с примерами работы с индексами (ключами) выше, метод updateValue(_:forKey:)
устанавливает значение для ключа, если оно не существует, или обновляет значение, если этот ключ уже существует. Однако, в отличие от индексов, метод updateValue(_:forKey:)
возвращает старое значение после выполнения обновления. Это позволяет проверить, состоялось ли обновление или нет.
if let oldScore = scores.updateValue(99, forKey: "Daisy") {
print("Счет Daisy был: \(oldScore)")
} else {
print("Нет игрока с данным именем...")
}
Данный метод возвращает одно из двух значений:
- Возвращает “nil”, когда ключ не присутствовал в словаре до его установки;
- Возвращает опциональное текущее значение, то есть «старое значение» ключа перед его установкой, если ключ присутствует в словаре.
Метод updateValue(_:forKey:)
возвращает опциональное значение, соответствующее типу значения словаря. Например, для словаря, который хранит String значения, метод возвратит String? тип, или “опциональный String”.
Работа со словарем
Также как и у массивов, можно узнать количество элементов в словаре через его read-only свойство count
:
print("The dictionary contains \(scores.count) items.")
// Output
// Выведет "The dictionary contains 2 items."
Логическое свойство isEmpty
можно использовать в качестве быстрого способа узнать, является ли свойство count
равным 0
- если словарь пустой, то вернет true
:
if scores.isEmpty {
print("The dictionary is empty.")
} else {
print("The dictionary is not empty.")
}
// Output
// Выведет "The dictionary is not empty."
Итерация по словарю
Можно сделать итерацию по парам “ключ-значение” в словаре с помощью “for-in” цикла. Каждое значение в словаре возвращается как кортеж (ключ, значение), и можно разложить части кортежа по временным константам или переменным в рамках итерации:
for (scoresName, scoresNumber) in scores {
print("\(scoresName): \(scoresNumber)")
}
Также, можно получить коллекцию ключей или значений словаря через обращение к его свойствам keys и values:
for scoresName in scores.keys {
print("Player name: \(scoresName)")
}
for scoresNumber in scores.values {
print("Player score: \(scoresNumber)")
}
Если необходимо использовать ключи или значения словаря вместе с каким-либо API, которое принимает объект по типу “Array”, то можно инициализировать новый массив с помощью свойств keys и values:
let scoresName = [String](scores.keys)
let scoresNumber = [String](scores.values)
Тип словаря в Swift является неупорядоченной коллекцией. Для итерации по ключам или значениям словаря в определенной последовательности, следует использовать метод sorted()
для свойств keys
или values
словаря.
Пример:
var scores = ["Bob": 42, "Alice": 10, "Daisy": 33]
print(scores.keys)
// Output
// ["Bob", "Daisy", "Alice"]
print(scores.keys.sorted())
// Output
// ["Alice", "Bob", "Daisy"]
for (scoresName, scoresNumber) in scores {
print("\(scoresName): \(scoresNumber)")
}
// Output
// Bob: 42
// Daisy: 33
// Alice: 10
for (scoresName, scoresNumber) in scores.sorted(by: <) {
print("\(scoresName): \(scoresNumber)")
}
// Output
// Alice: 10
// Bob: 42
// Daisy: 33
Создание словаря из массивов
С помощью встроенной глобальной функции zip()
можно соединить два массива в объект Zip2Sequence
, который затем передается в инициализатор типа Dictionary
:
let countries = ["Iran", "Iraq", "Syria", "Lebanon"]
let capitals = ["Tehran", "Bagdad", "Damascus", "Beirut"]
var seq = zip(countries, capitals)
var dict = Dictionary(uniqueKeysWithValues:seq)
for (key, value) in dict {
print("\(key) - \(value)")
}
В данном случае каждый элемент из массива countries
последовательно сопоставляется с соответствующим элементом из массива capitals
. Затем результат через параметр uniqueKeysWithValues
передается в инициализатор Dictionary
. И таким образом образуется словарь. Результат программы:
Iran - Tehran
Iraq - Bagdad
Syria - Damascus
Lebanon - Beirut
Стоит учитывать, что если в обоих массивах есть повторяющиеся значения, то подобный способ их объединения завершится с ошибкой (“Fatal error: Duplicate values for key: …”), ведь в словаре все ключи должны быть уникальными.
И для этого надо использовать другую форму инициализатора Dictionary
:
let countries = ["Iran", "Iraq", "Syria", "Lebanon", "Iran"]
let capitals = ["Tehran", "Bagdad", "Damascus", "Beirut", "Tehran"]
var seq = zip(countries, capitals)
var dict = Dictionary(seq, uniquingKeysWith:{return $1})
for (key, value) in dict {
print("\(key) - \(value)")
}
В данном случае в инициализатор в качестве первого параметра опять же передается объединенные последовательности. А второй параметр uniquingKeysWith
указывает на функцию, которая получает все значения из второго массива, которые соответствуют повторяющемуся ключу. В данном случае это два элемента. И затем надо возвратить какой-нибудь результат. Здесь просто возвращается значение второго параметра:
// Output
// Iran - Tehran
// Lebanon - Beirut
// Syria - Damascus
// Iraq - Bagdad
Добавление в словарь значение nil
Значение nil можно добавить при инициализации словаря:
var dict = [2: "Mary", 3: "Nansy", 6: "Olaph", 7: nil]
print(dict)
// Output
// [6: Optional("Olaph"), 3: Optional("Nansy"), 2: Optional("Mary"), 7: nil]
Но если добавить элемент обратившись по ключу и присвоить значение, то это действие удалит элемент из словаря:
dict[6] = nil
print(dict)
// Output
// [2: Optional("Mary"), 7: nil, 3: Optional("Nansy")]
Так же будет, если обратиться у элементу, у которого значение уже nil - этот элемент будет просто удален из словаря.
Чтоб добавить элемент со значением nil, необходимо задать переменную соответствующего типа со значением nil. После этого добавить эту переменную в качестве значения:
var nilValue: String?
dict[8] = nilValue
print(dict)
// Output
// [2: Optional("Mary"), 6: Optional("Olaph"), 8: nil, 3: Optional("Nansy"), 7: nil]
Либо используя метод updateValue(_:forKey:)
:
var dict: [Int: Int?] = [1: 345, 2: 45, 3: nil, 4: 1000]
print(dict)
// Output
// [3: nil, 4: Optional(1000), 1: Optional(345), 2: Optional(45)]
dict.updateValue(nil, forKey: 5)
print(dict)
// Output
// [5: nil, 3: nil, 4: Optional(1000), 1: Optional(345), 2: Optional(45)]
В обоих этих примерах можно увидеть, что к словарям было добавлено по одному элементу, у которых в качастве значения было присвоено “nil”.
Еще полезные ссылки
Также информацию по словарям можно получить на странице официальной документации (Узнать больше про методы и свойства словарей).
Ссылки на официальную документацию: