#swift
#swift
Вопрос:
У меня есть список кортежей с повторяющимися «идентификаторами»:
var tuples: [(Int, String)] = [
(1, "A"),
(1, "B"),
(1, "C"),
(2, "X"),
(2, "Y"),
(1, "Z")
]
Я хотел бы собрать их в массив пользовательского Bag
типа:
struct Bag {
let id: Int
var names: [String]
}
let desiredResult = [
Bag(id: 1, names: ["A", "B", "C", "Z"]),
Bag(id: 2, names: ["X", "Y"])
]
Каков наиболее эффективный способ достижения этой цели?
Комментарии:
1. Я предполагаю, что ваш желаемый результат
id: 2
X, Y
не должен бытьY, Z
2. @jnpdx Да, спасибо. Исправлено.
Ответ №1:
В Swift 5 самой простой реализацией было бы использовать native Dictionary(grouping:by:)
, который группирует значение так, как вы хотите, а затем использовать map
для создания вашего массива пакетов:
Dictionary(grouping: tuples, by: .0)
.map { Bag(id: $0.key, names: $0.value.map(.1)) }
Несколько простых объяснений: Dictionary(grouping: tuples, by: .0)
группирует ваш tuples
массив с помощью первого элемента ( .0
), что приводит к словарю типа:
let tuplesGrouped: [Int: [(Int, String)]] = [
1: [(1, "A"), (1, "B"), (1, "C"), (1, "Z")],
2: [(2, "X"), (2, "Y")],
]
Последний map
действует на тип [(key: Int, value: [(Int, String)])]
, который затем используется для создания массива Bag
s.
Комментарии:
1. Хорошо, но странно видеть замыкание в одном месте и путь к ключу в другом.
(grouping: tuples, by: .0)
было бы более последовательным.2. @Jessy Достаточно справедливо! Я согласен с этим мнением, и мне также нравится согласованность, поэтому я обновил код, чтобы он соответствовал тому, что вы упомянули
Ответ №2:
В обоих Dictionary
и OrderedDictionary
в настоящее время отсутствует инициализатор, который вам нужен для этого:
OrderedDictionary(grouping: tuples).map(Bag.init)
Не существует протоколов, которые позволили бы легко записать их для обоих типов. Ваш вариант использования, похоже, лучше подходит для OrderedDictionary
.
import struct Collections.OrderedDictionary
public extension OrderedDictionary {
/// Group key-value pairs by their keys.
///
/// - Parameter pairs: Either `Swift.KeyValuePairs<Key, Self.Value.Element>`
/// or a `Sequence` with the same element type as that.
/// - Returns: `[KeyValuePairs.Key: [KeyValuePairs.Value]]`
init<Value, KeyValuePairs: Sequence>(grouping pairs: KeyValuePairs)
where
KeyValuePairs.Element == (key: Key, value: Value),
Self.Value == [Value]
{
self =
OrderedDictionary<Key, [KeyValuePairs.Element]>(grouping: pairs, by: .key)
.mapValues { $0.map(.value) }
}
/// Group key-value pairs by their keys.
///
/// - Parameter pairs: Like `Swift.KeyValuePairs<Key, Self.Value.Element>`,
/// but with unlabeled elements.
/// - Returns: `[KeyValuePairs.Key: [KeyValuePairs.Value]]`
init<Value, KeyValuePairs: Sequence>(grouping pairs: KeyValuePairs)
where
KeyValuePairs.Element == (Key, Value),
Self.Value == [Value]
{
self.init( grouping: pairs.map { (key: $0, value: $1) } )
}
}
Ответ №3:
Простая / наивная реализация (которая ищет существующее совпадение для добавления, в противном случае оно добавляется к верхнему массиву:
var desired = [Bag]()
for tuple in tuples {
if let index = desired.firstIndex(where: { $0.id == tuple.0 }) {
desired[index].names.append(tuple.1)
} else {
desired.append(Bag(id: tuple.0, names: [tuple.1]))
}
}