Swift - find object in array of enums with associated values
Asked Answered
P

3

6

I have this enum:

enum Animal {       
    case cat(CatModel)
    case dog(DogModel)
}

And an array of animals:

var animals: [Animal]

I need to find a Cat object in this array by a property that Dog doesn't have. litterBoxId for example.

let cat = animals.first(where: {$0.litterBoxId == 7})

This of course has an error:

Value of type 'MyViewController.Animal' has no member 'litterBoxId'

How can I accomplish this? I also tried

($0 as CatModel).litterBoxId
Ptomaine answered 20/8, 2020 at 22:33 Comment(0)
S
8

You can use pattern matching to accomplish this with 2 ways.

Using switch:

let cat = animals.first(where: {
    switch $0 {
    case .cat(let catModel) where catModel.litterBoxId == 7:
        return true
    default:
        return false
    }
})

or if:

let cat = animals.first(where: {
    if case .cat(let catModel) = $0, catModel.litterBoxId == 7 {
        return true
    }
    return false
})

Update: As @Alexander-ReinstateMonica mentioned in his commnet, it would be more appropriate to hide this logic behind a function like this:

extension Animal {
    func matches(litterboxID: Int) -> Bool {
        switch self {
        case .cat(let catModel) where catModel.litterBoxId == 7:
            return true
        default:
            return false
        }
    }
}

and then your code will be much cleaner:

let cat = animals.first(where: { $0.matches(litterboxID: 7) })
Scheffler answered 20/8, 2020 at 22:54 Comment(4)
I wouldn't recommend this, it's a total mess that obscures intent. I would suggest to at leasst extract these into functions on the enum.Ossicle
@Alexander-ReinstateMonica so, a function in the enum that would take the array of enums and the litterBoxId as parameters? Did I understand you correctly?Scheffler
At the most superficial level, you could introduce func litterboxID(of: Animal, matches: Int) -> Bool, and put your pattern matching logic in there, so you can just call animals.first(where: { litterboxID(of: Animal, matches: 7) }. Better yet, you should put access to those fields in the enum itself, as I mention in my answer (and the comments to it). Better better yet, use a protocol that can contain these fields (if applicable) and allows you easily to cast to get the restOssicle
@Alexander-ReinstateMonica thank you. I update my answer with one of your suggestions.Scheffler
O
1

This probably isn't a good use of enums, but here is how it could work:

struct CatModel {
    let litterBoxID: Int
}

struct DogModel {
    let litterBoxID: Int
}

enum Animal {       
    case cat(CatModel)
    case dog(DogModel)
    
    var litterBoxId: Int {
        switch self {
        case .cat(let cat): return cat.litterBoxID
        case .dog(let dog): return dog.litterBoxID
        }
    }
}

var animals: [Animal] = []
let cat = animals.first(where: { $0.litterBoxId == 7 })

You would be much better off using a protocol:

struct CatModel: Animal {
    let litterBoxID: Int
}

struct DogModel: Animal {
    let litterBoxID: Int
}

protocol Animal {
    var litterBoxID: Int { get }
}

var animals: [Animal] = []
let cat = animals.first(where: { $0.litterBoxID == 7 })
Ossicle answered 20/8, 2020 at 22:45 Comment(2)
Part of the specification though was that DogModel doesn't have litterBoxID.Ptomaine
@Ptomaine Then you can make the litterBoxId field optional, and write { $0.litterBoxID? == 7 }. Alternatively, you can make var catModel: Cat? and var dogModel: Dog?, and write { $0.cat?.litterBoxID == 7 }. In either case, the much more natural approach is to use a protocol (to contain whatever properties happen to be in common), and cast for the parts that are specific: { ($0 as? Cat)?.litterBoxID == 7 }.Ossicle
T
0

You could add an extension that gives you back an array of CatModel

extension Array where Element == Animal {
var cats: [CatModel] {
    var filteredCats = [CatModel]()
    self.forEach { animal in
        switch animal {
        case .cat(let catModel): filteredCats.append(catModel)
        case .dog: break
        }
    }
    return filteredCats
}

}

let cat = animals.cats.first(where: {$0.litterBoxId == 7})
Tomasz answered 24/7, 2023 at 16:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.