Swift Switch Pattern Matching with Arrays

Swift switch pattern matching with arrays

You could define a custom pattern matching operator
~= which takes an array as the "pattern" and a value:

func ~=<T : Equatable>(array: [T], value: T) -> Bool {
return array.contains(value)
}

let foo = [1, 2, 3]
let bar = [4, 5, 6]

let value = 5

switch value {
case foo:
print("\(value) is in foo")
case bar:
print("\(value) is in bar")
default:
break
}

Similar operators exist already e.g. for intervals:

public func ~=<I : IntervalType>(pattern: I, value: I.Bound) -> Bool

Swift Pattern match on ArrayAny

Unfortunately casting between generic types like Array is not fully supported (yet). There are also odd situations even if you want to upcast:

let emptyStringArray : [String] = []
emptyStringArray as [Any] // succeeds

let stringArray : [String] = ["Bob", "Roger"]
stringArray as [Any] // error! due to the elements?!

let intArray = [1, 2, 3]
intArray as [Any] // error

let customStructArray : [myStruct] = []
customStructArray as [Any] // '[myStruct]' is not convertible to '[Any]'

There is also no good workaround without using a protocol. If you really want to have this dynamic behavior you could use reflections with the reflect() function. In Swift 2 they are more powerful, but it is still not a good solution.

Edit:

A solution with a protocol which gets adopted by all Arrays through an extension (only for your specific case):

protocol ArrayType {
var anyValues: [Any] { get }
}

extension Array: ArrayType {
var anyValues: [Any] {
return self.map { $0 as Any }
}
}

// now the switch gets rewritten as
switch any {
case let array as ArrayType:
let anyArray = array.anyValues
return "Array"
case let array as NSArray:
return "NSArray"
default:
return "Default"
}

Is there a way to compare array with array using switch case in Swift?

You can use cases with where predicates:

let array = ["A", "B"]

switch array {
case _ where array == ["A", "B"]: print("AB")
case _ where array == ["C", "D"]: print("CD")
default: print("default")
}

If you really wanted, you could define a pattern match operator (~=) that calls ==. The switch statement looks for definitions of the pattern match operator that accept the given pattern and candidate to determine whether a case is matched:

let array = ["A", "B"]

func ~= <T: Equatable>(pattern: [T], candidate: [T]) -> Bool {
return pattern == candidate
}

switch array {
case ["A", "B"]: print("AB")
case ["C", "D"]: print("CD")
default: print("default")
}

I would advise against this, however, because it's not clear whether such a case is doing a == check, contains(_:), hasPrefix(_:), etc.

Swift switch case to use contents of array, syntax?

Yes:

let puzzleInput = "great minds think alike"
var puzzleOutput = ""
let vowels: [Character] = ["a", "e", "i", "o", "u", " "]

for character in puzzleInput.characters {
switch character {
case _ where vowels.contains(character):
continue
default:
puzzleOutput.append(character)
}
}

case matching in Swift relies on the pattern matching operator (~=). If you define a new overload for it, you can shorten the code even more:

func ~=<T: Equatable>(pattern: [T], value: T) -> Bool {
return pattern.contains(value)
}

for character in puzzleInput.characters {
switch character {
case vowels:
continue
default:
puzzleOutput.append(character)
}
}

String pattern matching in Swift switch statements

I found the issue. I did not isolate the problem correctly.
The issue was that my source data had a slightly different "-" character which Swift (rightly so) did not consider equal to the condition in the switch cases. I sanitized the inputs and now it works correctly. In the playground I did manually write the input so the issue did not arise.

Thank you so much anyway!

Switch statement where value is Int but case can contain array

You can use case let with where for that.

let intValues = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
let inputValue = 30 // or some other int value
switch inputValue {
case let x where intValues.contains(x):
// do something more:
case 101:
// do something lol
case 13131:
// do another thing
default:
// do default
}

Switch on Array type

In Swift 4.1, you get a better error message (thanks to #11441):

Collection downcast in cast pattern is not implemented; use an explicit downcast to '[B]' instead

In short, you're hitting a bit of the compiler that isn't fully implemented yet, the progress of which is tracked by the bug SR-5671.

You can however workaround this limitation by coercing to Any before performing the cast:

class A {}
class B : A {}

let aArray : [A] = [B(), B()]

switch aArray /* or you could say 'as Any' here depending on the other cases */ {
case let (bArray as [B]) as Any:
print("bArray = \(bArray)")
default:
print("unknown type")
}

// bArray = [B, B]

Why does this work? Well first, a bit of background. Arrays, dictionaries and sets are treated specially by Swift's casting mechanism – despite being generic types (which are invariant by default), Swift allows you to cast between collections of different element types (see this Q&A for more info).

The functions that implement these conversions reside in the standard library (for example, Array's implementation is here). At compile time, Swift will try to identify collection downcasts (e.g [A] to [B] in your example) so it can directly call the aforementioned conversion functions, and avoid having to do a full dynamic cast through the Swift runtime.

However the problem is that this specialised logic isn't implemented for collection downcasting patterns (such as in your example), so the compiler emits an error. By first coercing to Any, we force Swift to perform a fully dynamic cast, which dispatches through the runtime, which will eventually wind up calling the aforementioned conversion functions.

Although why the compiler can't temporarily treat such casts as fully dynamic casts until the necessary specialised logic is in place, I'm not too sure.

JavaScript FIzzBuzz using Tuples similar to Swift's pattern matching

switch/case compares values using the === operator. This compares arrays by identity, not by contents, so you can't use it with array literals as you've tried.

JavaScript doesn't have tuples. div3, div5 uses the comma operator, which simply evaluates it to the value of div5.

If you want something like your coding pattern, I think the best you can do is use JSON.

function fizzBuzz(numbers) {
for (number in numbers) {
let div3 = number % 3 == 0 ? true : false;
let div5 = number % 5 == 0 ? true : false;

switch (JSON.stringify([div3, div5])) {
case '[true,true]':
console.log("FizzBuzz");
break;
case '[true,false]':
console.log("Fizz");
break;
case '[false,true]':
console.log("Buzz");
break;
case '[false,false]':
console.log(number);
break;
}
}
}

var array = [...Array(100).keys()];

fizzBuzz(array);


Related Topics



Leave a reply



Submit