Уменьшить массив объектов на основе поля объекта

#arrays #swift

#массивы #swift

Вопрос:

У меня есть Country объект и City объект

 struct Country: {
  let name: String
  let countryCode: String
  let cities: [City]
  let population: Int

  init(name: String, countryCode: String, cities: [City], population: Int) { 
    self.name = name 
    self.countryCode = countryCode
    self.cities = cities
    self.population = population
  }
}

struct City {
  let id: Int
  let name: String
  let latitude: Double
  let longitude: Double
  let countryCode: String
  let population: Int
}
  

Входящие JSON данные выглядят следующим образом, которые декодируются в [City] массив

 {
   "cities":[
      {
         "id":1,
         "name":"Paris",
         "latitude":0,
         "logitude":0,
         "country_code":"FR",
         "population":0
      },
      {
         "id":2,
         "name":"Nice",
         "latitude":0,
         "logitude":0,
         "country_code":"FR",
         "population":0
      },
      {
         "id":3,
         "name":"Berlin",
         "latitude":0,
         "logitude":0,
         "country_code":"DE",
         "population":0
      },
      {
         "id":4,
         "name":"Munich",
         "latitude":0,
         "logitude":0,
         "country_code":"DE",
         "population":0
      },
      {
         "id":5,
         "name":"Amsterdam",
         "latitude":0,
         "logitude":0,
         "country_code":"NL",
         "population":0
      },
      {
         "id":6,
         "name":"Leiden",
         "latitude":0,
         "logitude":0,
         "country_code":"NL",
         "population":0
      }
   ]
}
  

Как я мог бы эффективно создать [Country] массив из [City] массива? Я пытался использовать reduce:into: , но не уверен, что это то, что я должен использовать.

Я знаю, что мог бы использовать пустой массив и добавлять / создавать страны одну за другой, затем искать, если она уже есть, и добавлять к ней город. Это создает ужасно выглядящий код, как для меня. Я чувствую, что есть элегантное решение этой проблемы с использованием функций map или reduce.

reduce:into: код, который я пробовал до сих пор

 func transformArrayOf(_ cities: [City]) -> [Country] {

  let empty: [Country] = []
        
  return cities.reduce(into: empty) { countries, city in
          
    let existing = countries.filter { $0.countryCode == city.countryCode }.first
    countries[existing].cities.append(city)
  }
}
  

Редактировать:

Функция получает только [City] массив. Поэтому страны должны создаваться только из этого.

Dictionary(grouping:by:) с map(_:) отлично работает! Две строки вместо вложенных for циклов и if операторов 🙂

И Country имя может быть проанализировано из кода страны

Комментарии:

1. JSON Это деталь реализации. Если вы хотите, чтобы мы могли запускать эти фрагменты и могли лучше помогать, я предлагаю вам заменить JSON текст литералом массива, содержащим жестко запрограммированные City экземпляры struct . Таким образом, нам не нужно разбираться в логике декодирования json только для запуска этого кода

2. Как создаются ваши страны? Они содержат данные, которые не получены из городов (например name , countryName (чем они отличаются?) И т. Д.

Ответ №1:

Используйте Dictionary(grouping:by:) и map(_:) комбинируйте, чтобы получить ожидаемый результат.

 let countries = Dictionary(grouping: cities, by: { $0.countryCode }).map { (countryCode, cities) -> Country in
    return Country(name: "", countryCode: countryCode, countryName: "", cities: cities, population: cities.reduce(0) { $0   $1.population })
}
  

Поскольку значения для name и countryName неизвестны, я использовал empty String ( "" ) для обоих.

Комментарии:

1. Создание экземпляра Country с пустыми значениями на самом деле не добавляет никакого значения, поэтому этот ответ выглядит для меня как дубликат. И ваше предположение, что все люди живут в городах, неверно.

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

3. Нет, свойства let объявлены 🙂 Было бы лучше узнать из OP, откуда должны поступать данные по странам из IMO

Ответ №2:

Это то, что Dictionary(grouping:by:) для:

 let citiesByCountryCode = Dictionary(grouping: cities, by: .countryCode)
  

Но вам понадобится отдельная логика для создания стран, потому что они содержат данные, которые не являются производными от городов, таких как name countryName (чем они отличаются?) и т.д.