Nobadi
Nobadi

Reputation: 231

Swift - How to combine two unordered arrays of structs into an array of new objects based on matching values?

I have two different arrays of custom Structs, (let's say [NameAndAge] and [FullName]). I would like to combine them into an array of new objects (let's call it FullNamesAndAges). My thinking is to iterate over them, matching first names in order to create the new object.

If my structs look like this:

struct NameAndAge {
    let name: String
    let age: Int
}

struct FullName {
    let firstName: String
    let lastName: String
}

And my new object looks like this:

class PersonViewModel {
    let firstName: String
    let lastName: String
    var age: Int
    // with initializer
}

How would I accomplish this? Right now, I am using two maps, but I am wondering if there is a shorter/cleaner/more-efficient/better (insert your preferred adjective) way to do this? I completely understand that this may be subjective, since it's a matter of opinion. I just want to know if there is a faster way to accomplish this.

What I currently have:

let namesAndAges: [NameAndAge] = // pretend this is an array of 10+ structs, unordered
let fullNames: [FullName] = // pretend this is an array of 10+ structs, unordered


let people = namesAndAges.compactMap { nameAge in
                        fullNames.compactMap { fullName in
                              if fullName.firstName == nameAge.name {
                                       return PersonViewModel(fullName.firstName, fullName.lastName, nameAge.age)
                              }
                        }
              }

To me that looks super sloppy, which is why I'm hoping there's a 'better' way to do it. I can't use zip since they're unordered.

Upvotes: 0

Views: 271

Answers (1)

vacawama
vacawama

Reputation: 154573

Your code is compact, but it isn't very efficient. If your arrays had thousands of entries, you'd be doing millions of comparisons.

Instead, first create a Dictionary that allows you to lookup a NameAndAge record based upon the name. Dictionaries are very efficient for lookup.

Then use that dictionary in the compactMap of the fullNames array to create the final array of PersonViewModels:

let namesAndAges: [NameAndAge] = [] // this is an array of 10+ structs, unordered
let fullNames: [FullName] = [] // this is an array of 10+ structs, unordered

// Create a dictionary to efficiently map name to `NameAndAge` struct
var nameAndAgeLookup = [String : NameAndAge]()
namesAndAges.forEach { nameAndAgeLookup[$0.name] = $0 }

let people = fullNames.compactMap { fullName -> PersonViewModel? in
    guard let age = nameAndAgeLookup[fullName.firstName]?.age else { return nil }
    return PersonViewModel(fullName.firstName, fullName.lastName, age)
}

Note: I assume this is just an example, because looking up someone's age based on their firstName is not really a good idea due to name collisions.

Upvotes: 1

Related Questions