Less verbose optional casting in case statement?

Can anybody let me know if there is a tidier way of dealing with optional casting please, so that my code is not as versbose?

    func organizeFruits(data: [String : Any]) {
        for (key, value) in data {
            switch key {
            case Fruits.apple.rawValue : fruitLifespanApple = value as? String ?? "MISSING: fruitLifespanApple"
            case Fruits.orange.rawValue : fruitLifespanOrange = value as? String ?? "MISSING: fruitLifespanOrange"
            case Fruits.redGrape.rawValue : fruitLifespanRedGrape = value as? String ?? "MISSING: fruitLifespanRedGrape"
            case Fruits.pineapple.rawValue : fruitLifespanPineapple = value as? String ?? "MISSING: fruitLifespanPineapple"
            case Fruits.pear.rawValue : fruitLifespanPear = value as? String ?? "MISSING: fruitLifespanPear”
            default: print("MISSING : organizeFruits : \(key)")
            }
        }
    }

It seems Fruit is an enum, one you'll iterate a lot at that. switch (and repeated code) might be ill-suited for such use, could you make it CaseIterable & Hashable?

1 Like

I did not withdraw the post.

The author of the comment (aka @suyashsrijan) withdrawn it

Edit:
Also wrong closing double quote (") on pear case

Yes, I had a problem that I raised with Apple on this, but I couldn't repeat it. I thought that I got all of those, but one slipped through. It will come in handy.

I thought it meant the thread.

Could you elaborate more on that? I don't get it.

Sorry, it's off topic. But I filed a bug with the issue being double quotes tripping code up. If writing code freestyle in Notes the quotes would not compile, but they couldn't repeat the issue. This just reminds me of the matter, and I should be able to repeat it again, because I couldn't myself !

CaseIterable looks useful, I thought switching over an enumeration was a slog to program, so this could be useful..

1 Like

Hi @powing – when asking questions like this, it often helps to make sure your example code is self-contained. That way people can cut and paste it into a temporary file to play around with ideas they might then suggest you try.

In this case, a helper might help cut down the verbosity:

func organizeFruits(data: [String : Any]) {
  for (key, value) in data {
    let lifespan: (Any) -> String = { $0 as? String ?? "MISSING: lifespan for \(key)" }

    switch key {
    case Fruits.apple.rawValue: fruitLifespanApple = lifespan(value)
    case Fruits.orange.rawValue: fruitLifespanOrange = lifespan(value)
    case Fruits.redGrape.rawValue: fruitLifespanRedGrape = lifespan(value)
    case Fruits.pineapple.rawValue: fruitLifespanPineapple = lifespan(value)
    case Fruits.pear.rawValue: fruitLifespanPear = lifespan(value)
    default: print("MISSING : organizeFruits : \(key)")
    }
  }
}

Alternatively, you could pre-process the data into a more strongly-typed sequence:

func organizeFruits(data: [String : Any]) {
  let checkedFruit: [(Fruits,String)] = data.flatMap { k,v in
    guard let fruit = Fruits(rawValue: k) else { print("Invalid Fruit: \(k)"); return nil }
    return (fruit, v as? String ?? "MISSING: lifespan for \(k)")
  }

  for (key, value) in checkedFruit {
    switch key {
    case .apple: fruitLifespanApple = value
    case .orange: fruitLifespanOrange = value
    case .redGrape: fruitLifespanRedGrape = value
    case .pineapple: fruitLifespanPineapple = value
    case .pear: fruitLifespanPear = value
    // note, no need for default this time :)
    }
  }
}

Finally, are you sure you need hard-coded variables for each of the fruit types? It might be better building a dictionary that maps fruit to lifespans than declaring lots of variables (i.e. take the checkedFruit array above and pass it into Dictionary(uniqueKeysWithValues:) initializer).

2 Likes

Ben_Cohen to my rescue, many thanks as I was just about beat trying to figure out how to switch on a dictionary with the enum values?

The example is not actually what I'm using, I'm working in SpriteKit and there's a lot of janky stuff I have to do to make things work, but your probably right in your closing comments, perhaps another time.

You're also right about providing test data with a forum posting, I just thought that it might be too verbose and people just want to see the meat, I'll bear it in mind for next time (and there will be a next time).

Lantua: Many thanks for the reference to CaseIterable, it will definitely come in handy. Ordering the enum was simple enough, but I just couldn't figure out how to switch on the enum with the key from a dictionary, but thankfully I had help.

btw: flatMap is now compactMap

1 Like

To expand on Dictionary(uniqueKeyWithValues:), it would look something like this.

func organizeFruits(data: [String : Any]) {
  let checkedFruit: [(Fruits,String)] = data.flatMap { k,v in
    guard let fruit = Fruits(rawValue: k) else { print("Invalid Fruit: \(k)"); return nil }
    return (fruit, v as? String ?? "MISSING: lifespan for \(k)")
  }

  // This will crash if key collides
  let lifespans1 = Dictionary(uniqueKeysWithValues: checkedFruit)

  // If you have logic to merge duplicated keys
  let lifespans2 = Dictionary(checkedFruit) { former, latter in
    latter // uses value that appears last in the list
  }
}

This would require that Fruit conforms to Hashable, but since it's simple enum, it's just a matter of adding Hashable to the declaration.

Don't worry about it, if you put it inside a code block, this forum does put a size limit (and provide scroller) for you. So it won't be too large, and we'll all be on the same page too :wink:.

func organizeFruits2(data: [String : Any]) {
    for (key, value) in data {
        let value = value as? String ?? "MISSING: fruitLifespan \(key)"
        switch Fruits(rawValue: key) {
        case .apple?: fruitLifespanApple = value
        case .orange?: fruitLifespanOrange = value
        case .redGrape?: fruitLifespanRedGrape = value
        case .pineapple?: fruitLifespanPineapple = value
        case .pear?: fruitLifespanPear = value
        case nil: print("MISSING : organizeFruits : \(key)")
        }
    }
}

You could possibly also have a dictionary of Fruits and keyPaths to their corresponding variables to make mapping values to variables even easier.

Terms of Service

Privacy Policy

Cookie Policy