Archive for January, 2018

Pattern match style filtering

I’ve written about this before, but a question came up recently that I thought was worth posting, as it’s a much simpler case than the one I wrote about last year.

Byrre_b asks:

Is there any way to write “pattern matching style filtering” in a better way then using a complete `if case` statement?

Such as:

let values: [NonEquatableEnum] = [...]
let filtered = values.filter { val in
    if case .thatOneInterestingValue = val {
        return true
    }
    return false
}

Note: Several people have pointed out if the enumeration is equatable, just use == rather than pattern matching. You can match case, even with associated values with, e.g. if case .foo = value.

You can filter using the pattern match operator, as shown here, or for equatable enumerations with ==.

enum NonEquatableEnum { case nah, blah, thatOneInterestingValue }

let values: [NonEquatableEnum] = [.nah, .blah, .nah, .thatOneInterestingValue, .nah, .thatOneInterestingValue, .blah]
let filtered = values.filter({ $0 ~= .thatOneInterestingValue })

Although this stores all values matching your subject case into filtered, the results aren’t very meaningful unless you want to count how many instances of .thatOneInterestingValue appear. That’s because filtering by enumeration case is usually limited to two situations:

  • You’re working with a structure and using the enumeration as a tag for filtering
  • You’re working with associated values and want to collect the enumeration cases and then extract the values.

The first of these is made simple with Swift 4 key paths. For example, consider the following structure:

struct Foo {
    var (x, y, z) = (0, 0, 0)
    let numnum: NonEquatableEnum
    init(_ n: NonEquatableEnum) { self.numnum = n }
}

let values2: [Foo] = [Foo(.nah), Foo(.blah), Foo(.nah), Foo(.thatOneInterestingValue), Foo(.nah), Foo(.thatOneInterestingValue), Foo(.blah)]

Assuming each instance has some more interesting data than the default (0, 0, 0) triple, pull out tagged instances using the same filter approach:

let kp = \Foo.numnum
let filtered2 = values2.filter({ $0[keyPath: kp] ~= .thatOneInterestingValue })

The key path lets you “dive” into each struct to test the enumeration member, while preserving the data stored in the other structure members. Instead of just counting how many instances of a simple enumeration there are, it acts as a meaningful filtering operation.

The second challenge, retrieving associated values, is more complex, as explained in my original write-up. Hand-crafting a result with if case gets you the values you need.

enum MoreComplicated {
    case one(Int)
    case two(Int)
    case three(String, String)
}

let values3: [MoreComplicated] = [.one(3), .two(5), .two(2), .three("hi", "there")]

Here’s an example that pulls out the case three enumerations:

let results2 = values3.filter({
    if case .three = $0 { return true } else { return false }
})

If you want to filter and extract at the same time,  add let declarations into your if case statement and switch the filter operation to a flatMap :

let results3 = values3.flatMap({
    (value: MoreComplicated) -> (String, String)? in 
        guard case .three(let x, let y) = value
            else { return nil }
        return (x, y)
    })

This returns an array of tuples, containing the associated values for each matching enumeration case.

Thoughts? Improvements? Fixes? Drop a note, tweet, or email to let me know!