Сбор данных для повторяющихся идентификаторов в массив пользовательского типа

#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]))
    }
}