4

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
soleil
  • 12,133
  • 33
  • 112
  • 183

3 Answers3

4

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) })
gcharita
  • 7,729
  • 3
  • 20
  • 37
  • 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. – Alexander Aug 21 '20 at 01:09
  • @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? – gcharita Aug 21 '20 at 07:56
  • 1
    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 rest – Alexander Aug 21 '20 at 13:20
  • @Alexander-ReinstateMonica thank you. I update my answer with one of your suggestions. – gcharita Aug 21 '20 at 16:13
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 })
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • Part of the specification though was that DogModel doesn't have litterBoxID. – soleil Aug 21 '20 at 02:16
  • 1
    @soleil 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 }`. – Alexander Aug 21 '20 at 02:36
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})
Jevon718
  • 354
  • 4
  • 10