2

I have a Country object and a City object

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
}

Incoming JSON data looks like this which decodes into [City] array

{
   "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
      }
   ]
}

How would I create [Country] array from [City] array efficiently? I've tried to use reduce:into: but not sure that's what I have to use.

I know I could go with an empty array and add/create Countries one by one then search if there is one already and add City to it. That creates awful looking code as for me. I feel like there is an elegant solution to this problem using map or reduce functions.

reduce:into: code I've tried so far

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

EDIT:

The function only gets [City] array. So countries must be created only from that.

Dictionary(grouping:by:) with map(_:) works perfectly! Two lines instead on nested for loops and if statements :)

And Country name can be parsed from a country code

2
  • The JSON is an implementation detail. If you want us to be able to run these snippets and be able to help better, I suggest you replace the JSON text with a array literal containing hard-coded City struct instances. That way we don't have to figure out the json decoding logic just to run this code Commented Sep 24, 2020 at 12:49
  • How are your countries created? They contain data that isn't derived from the cities (e.g. name, countryName (how are those different?), etc. Commented Sep 24, 2020 at 12:51

2 Answers 2

3

Use Dictionary(grouping:by:) and map(_:) combined to get the expected result.

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

Since the values for name and countryName are unknown, I've used empty String ("") for both.

Sign up to request clarification or add additional context in comments.

3 Comments

Instantiating Country with empty values really doesn't add any value so this answer looks like a duplicate to me. And your assumption that all people live in cities is not correct.
@JoakimDanielson I don't think this is a duplicate. And about the empty Strings, I've already mentioned that they are unknown and OP can fill in himself.
No, the properties are let declared :) It would be better to find out from OP where the data for the countries is supposed to come from IMO
0

This is what Dictionary(grouping:by:) is for:

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

But you'll need separate logic to create the countries, because they contain data that isn't derived from the cities like name, countryName (how are those different?), etc.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.