Merge An Array Item's Array Property Based On Property

The title might be confusing but here is an example data to give a clear understanding

struct City {
  var name: String?
  var businesses: [Business]?
}

So if i have a city array, i wish to merge the businesses array based on the city name since the city may have duplicates e.g.

[
  {
     name: "CIty 1"
     businesses: [
         { name: "Business 1" },
         { name: "Business 2" },
     ]
  },
 {
     name: "CIty 2"
     businesses: [
         { name: "Business 1" },
         { name: "Business 2" },
     ]
  }
  {
     name: "CIty 1"
     businesses: [
         { name: "Business 3" },
         { name: "Business 4" },
     ]
  }
]

In the data example above, there are 2 entries of City1. i am looking for a way to not have to use a dictionary, if it is possible to use the existing array and output it such that there is only 1 entry of City but will contain 4 entries of business.

Thoughts?

So, you have an array of City objects, each with a name and an array of Business objects. You want to have it so that cities with the same name (duplicates) are merged together, along with their businesses.

Example:

let business1 = Business(name: "Business 1")
let business2 = Business(name: "Business 2")
let business3 = Business(name: "Business 3")
let business4 = Business(name: "Business 4")

// before
let cities = [
    City(name: "City 1", businesses: [business1, business2]),
    City(name: "City 2", businesses: [business1, business2]),
    City(name: "City 1", businesses: [business3, business4])
]

// after
let cities = [
    City(name: "City 1", businesses: [business1, business2, business3, business4]),
    City(name: "City 2", businesses: [business1, business2])
]

If this is correct, then we can keep going.


Something like this can work:

// make City Equatable using only its name
// this is how the example differentiates between two cities
struct City: Equatable {
    ...

    static func == (lhs: City, rhs: City) -> Bool {
        lhs.name == rhs.name
    }
}

// this is the cities array without duplicate cities and with merged businesses
let newCities: [City] = cities.reduce([]) { result, city in
    var newResult = result

    if let index = result.firstIndex(of: city) {
        // gets messy with optionals
        newResult[index].businesses?.append(contentsOf: city.businesses ?? [])
    } else {
        newResult.append(city)
    }

    return newResult
}


Hopefully, this will work for your use case.

@Babyj here is the code. currently, i am doing it the manual way. which for me is not ideal. what do you think? Is there a way to simplify it?

the first initial fetch will not have duplicate city name. but the more list fetch for sure will have and that is the part where i wish to merge duplicate city names.

import SwiftUI

struct CityListView: View {
   
  private let MAX_DISPLAY_PER_FETCH = 20
  private var cityUrl: String?
   
    @State private var citiesList: [City]?
    @State private var html: String?
    @State private var index = 0
    @State private var showFooter = true
   
  init(cities: [City]? = nil) {
    self.citiesList = cities
    setupData()
  }
   
  var body: some View {
    if let citiesList {
      content(citiesList)
    }
    else {
      AsyncView(
        operation: { try await getCities() },
        content: { cities in
          content(cities)
        }
      )
    }
  }
   
  func content(_ cities: [City]) -> some View {
    return GeometryReader { geometryProxy in
      ScrollView {
        LazyVStack(alignment: .leading, spacing: 0) {
          if cities.count > 0 {
            ForEach(cities) { city in
              HStack {
                Text(city.header)
                  .bold()
                  .foregroundColor(.white)
                  .textCase(.uppercase)
                  .padding(.all, 8)
                Spacer()
              }
              .background(Color(UIColor(named: PrefTool.instance.getBoolean(Keyword.DARK_MODE) ? "DarkerGray" : "DarkGray")!))
             
              ForEach(city.businesses) { business in
                Text(business.name)
                 
                Divider()
              }
            }
          }
          else {
            Text("Empty List")
          }
        }
        .safeAreaInset(edge: .bottom, spacing: 0) {
          if showFooter {
            VStack {
              Text("Footer")
            }
            .padding()
            .frame(maxWidth: .infinity)
            // The background will extend automatically to the edge
            .background(Color.green)
            .onBecomingVisible {
              Task {
                do {
                  let moreList = try await getMoreCities(index)
                  if moreList.isEmpty {
                    showFooter = false
                    citiesList?.append(contentsOf: moreList)
                  }
                  else {
                     for more in moreList {
                      let index = citiesList?.firstIndex { $0.name == more.name } ?? -1
                      if index >= 0 {
                        citiesList?[index].businesses.append(contentsOf: more.businesses)
                      }
                      else {
                        cities?.append(more)
                      }
                    }
                  }
                } catch {

                }
              }
            }
          }
        }
      }
    }
  }
   
  func getCities() async throws -> [City] {
    let cachedIndex = index
    html = try await Web.getRequest(url)
    index = index + MAX_DISPLAY_PER_FETCH
    self.citiesList = CityParser.getCities(html ?? "", cachedIndex, MAX_DISPLAY_PER_FETCH)
    return self.citiesList ?? []
  }
   
  func getMoreCities(_ from: Int) async throws -> [City] {
    let cachedIndex = index
    index = index + MAX_DISPLAY_PER_FETCH
    return CityParser.getCities(html ?? "", cachedIndex, MAX_DISPLAY_PER_FETCH)
  }
   
}
Merge An Array Item's Array Property Based On Property
 
 
Q