0

I'm trying to figure out a way to retrieve all elements in a nested enum from an array. The way I built my nested enum as follows:

enum ItemType: Hashable {
    case sword(Swords) 
    case shield(Shields)  
    case armor(Armors) 
}

enum Swords: String, CaseIterable {
    case Sabre = "Sabre"
    case Xiphos = "Xiphos"
    case Broadsword = "Broadsword"
}

enum Shields: String, CaseIterable {
    case Buckler = "Buckler"
    case HeaterShield = "Heater Shield"
    case KiteShiled = "Kite Shield"

}

enum Armors: String, CaseIterable {
    case StudArmor = "Stud Armor"
    case Chainmail = "Chainmail"
    case PlateArmor = "Plate Armor"
}

Now I have an array that contains some of the elements from the ItemType enum:

let loots: [ItemType] = [
    .sword(.Xiphos),
    .sword(.Broadsword), 
    .sword(.Sabre),
    .armor(.StudArmor), 
    .armor(.Chainmail),
    .shield(.Buckler), 
    .shield(.HeaterShield)
]

Is there a way to get all elements under ItemType.sword into another array? I'm thinking along the line of: (obviously it doesn't work)

let item = loots.first(where: {$0 == ItemType.sword}) 

How can I achieve my goal? Or do I have to reconstruct my Enums in a different way?

Please let me know.

Thank you in advance.

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
Bob
  • 155
  • 1
  • 8
  • Does this answer your question? [Filter array of items by enum property with associated value](https://stackoverflow.com/questions/56988742/filter-array-of-items-by-enum-property-with-associated-value) – HangarRash Aug 20 '23 at 00:45

1 Answers1

0

You can add a computed property to your enum ItemType to check if it is a sword or not:

extension ItemType {
    var isSword: Bool {
        switch self {
        case .sword:
            return true
        default:
            return false
        }
    }
}

or if you prefer if case instead of switch:

extension ItemType {
    var isSword: Bool {
        if case .sword = self { return true }
        return false
    }
}

Them you can simply get the first item where isSword or filter them:

let item = loots.first(where: \.isSword) // sword(.Xiphos)

let items = loots.filter(\.isSword) // [{Xiphos}, {Broadsword}, {Sabre}]

Note that it is Swift naming convention to name all your enumeration cases starting with a lowercase letter.

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • Thank you so much! This worked great. However, I do have a follow up question. How do I pass something like .isSword as a parameter? As in some cases where I might want to filter for ItemType.shield or ItemType.armor. – Bob Aug 20 '23 at 02:11
  • "How do I pass something like .isSword as a parameter?" Not sure what you mean but I guess you are asking how to pass it in a closure. `where: \.isSword` can also be written as `{ $0.isSword }`. If you need `isShield` or `isArmor` you need to add those computed properties as well and filter `{ $0.isArmor || $0.isShield }`. Another option is to create an `isArmorOrShield` property and use it as a keyPath syntax. – Leo Dabus Aug 20 '23 at 03:51
  • What I wanted is to call a func and pass in a parameter to be used as the criteria for array filter. Func filterItem(*parameter*) { let items = loots.filter(*parameter*)} Hope I made myself clear. – Bob Aug 20 '23 at 04:23
  • @Bob If you need a func you can add `predicate: (ItemType) -> Bool` to your method signature. `func filterItem(predicate: (ItemType) -> Bool)` and then `loots.filter(predicate)` – Leo Dabus Aug 20 '23 at 04:32
  • I've figured it out: Func filterItem(myFilter: @escaping (ItemType) -> Bool). This way, I can call it using: filterItem(myFilter: {$0.isArmor}). Thanks again for all your help! – Bob Aug 20 '23 at 04:36
  • you are welcome. Note that you can also use keyPath syntax there `filterItem(myFilter: \.isArmor)` No need to pass a closure – Leo Dabus Aug 20 '23 at 04:36