Switch statement not iterating in For Loop?

“item” in my door loop, enclosing the switch statement, apparently isn’t used and I don’t think it’s even looping?!

I can extract values from a dictionary to an array, conforming to an enumeration, I then need to iterate over the vales to add matching functions to a new array, but the loop won’t work?

func stockCorrelation(checkShopItems: Bool, in shop: Dictionary<shopItems, Bool>) -> Any {
    
    var shopItemsInStock : [shopItems] = []

    
    
    for (checkKey, iterValue) in shop {
        if iterValue == checkShopItems {
            shopItemsInStock.append(checkKey)
        }
    }
    
    for item in shopItemsInStock {
        switch shopItemsInStock {
        case [.aspirin] : return dispenseAspirin()
        case [.paracetamol] : return dispenseParacetamol()
        case [.pollen] : return dispensePollen()
        case [.vitA]  : returndispenseVitB()
        case [.vitB] : return dispenseVitB()
        case [.calcium] : return dispenseCalcium()
        default : print(“out of pills“)
            
            
        }
        
    }
    return shopItemsInStock
}

I am unsure if a switch statement is the best option, but if you can imagine that there are so many "if else" conditions to choose cater for (never mind adding new items easily in the future) that I wanted something succinct.

Do you mean this?

for item in shopItemsInStock {
    switch item { // ← This is the crux of the change.
    case .aspirin: dispenseAspirin()
    case .paracetamol: dispenseParacetamol()
    case .pollen: dispensePollen()
    case .vitA: dispenseVitB()
    case .vitB: dispenseVitB()
    case .calcium: dispenseCalcium()
    default : print(“out of pills“)
    }
}

Your current code switches on the entire array, though your loop does so repeatedly according to the array’s count. It checks whether the entire array matches several possible variants, which are each single‐element arrays. The suggestion would switch over only the entry each time instead.

Another problem is that you have return statements throughout the switch. return causes the function to exit, so on the first iteration, unless it is out of pills, it will return and never get to the next iteration of the loop.

2 Likes

I feel a fool (again). Such an obvious mistake. And thank you for the step by step process of what was going on.

The return option I didn't realise would exit, but of course that makes logical sense.

No worries. Mistakes are the most effective way to learn.

Thanks a lot, just learned switch can be used with collections. Just curious is this a new feature ?

1 Like

Anything that is Equatable can be switched (if Google is to be trusted).

The official documentation does not mention Equatable, but it does list several other advanced switch uses.

1 Like

Thanks, as you pointed out I think it is looking for Equatable for it to work.

struct S : Equatable {}

let s1 = S()
let s2 = S()

switch s1 {
case s2: //If it wasn't Equatable, this line throws a compilation error 
    print("cool")
default:
    print("not so cool")
}

The explanation is actually hidden within language reference regarding case label and expression pattern.

Expression patterns are, to put simply, any expression that can be matched using ~= operator.
AFAIK, Swift provide default implementation only for Equatable of the same type, but other types, such as Range family, also provide their own implementations.

let number = 1, range = 0..<2
number ~= 1 // true; Equatable of the same type, using `==` implementation
range ~= 1 // true; Range<Int> against Range<Int>.Bound

Since switch‘s case can use any pattern (check the language raference), you can use expression pattern, and so:

switch value {
case expression: break
default: break
}

behaves identically as:

if expression ~= value {
    // do something
}

The implications are that

  1. You can use Range family within switch‘s case.
switch 2 {
case 1...3: break
default: break
}
  1. You can create your own matching. Note that order of ~= parameter matters.
func ~=(_ lhs: Int, _ rhs: String) -> Bool {
    return rhs.count == lhs
}

// This works fine
switch "Test" {
case 4: print(“Four-character long”)
default: break
}

// Should have error here
switch 4 {
case "Test": break
default: break
}
3 Likes

Thank you much ... this is really nice, so far I had just been matching only equatable, didn't realise how range was getting matched.

What is ~= called as ? Based on what I understand from your post, we can have our own custom implementation to match different types. Could it be read as "translates / equivalent to" ?

You can totally matches different types, so long as there’s a proper ~= implemented.

From here, it’s called pattern match so I’d read pattern ~= value as pattern matches value.

Equal should be reserved for ==. Also that equivalence in Swift tend to apply to 2 objects of the same type, while pattern matching has no such restriction.

1 Like

Thanks a lot, what started of as a small doubt, ended up learning new things that I didn't know existed.

1 Like