ato3787045
ato3787045

Reputation: 195

Swift: assign to variable in switch case-let-where

Is it possible to make an assignment to the artist variable before it is used in the where subclause?

var artist
switch fullline {
case let path where path.hasPrefix("Monet"):
    artist = "Monet"
case let path where path.hasPrefix("Cezanne"):
    artist = "Cezanne"
default: ()
}

Closure:

case let path where { () -> Bool in let artist = "Monet"; return path.hasPrefix(artist) }:

Error:

() -> Bool' is not convertible to 'Bool'

Context:

I have lines of freeform text with artist name as the prefix that requires massaging to output consistent humanly readable text. e.g.

Monet : Snow at Argenteuil 02, 1874
Monet - Snow at Argenteuil, 1874, 3rd Floor Collections
Monet, Claude - 1875, Snow in Argenteuil
Cezzane - Vase of Flowers, 1880-81, print
Cezzane, Paul 1900-1903 Vase of Flowers
Cezzane - Vase with Flowers, 1895-1896

There will be a code fragments that performs detailed processing/categorizing for each artist. Hence the processing logic is artist dependent.

I would like to define similar to the following construct

switch fullline
hasPrefix(artist = "Monet")  
    -> code logic 1
       get_birthday(artist)

hasPrefix(artist = "Cezzane")
    -> code logic 2
       get_birthday(artist)

Upvotes: 4

Views: 18551

Answers (3)

aryaxt
aryaxt

Reputation: 77616

You can achieve this by switching over a tuple of your enum and your optional. Optional is an enum too, so you can switch both of them

enum SomeSnum {
   case a, b, c
}

let someString: String? = "something"
let esomeEnum = SomeSnum.b

switch(esomeEnum, someString) {
case (.b, .some(let unwrappedSomething)) where unwrappedSomething.hasPrefix("so"):
    print("case .b, \(unwrappedSomething) is unwrapped, and it has `so` prefix")

case (.a, .none):
    print("case .a, and optional is nil")

default:
    print("Something else")
}

You can also do an if statement

if case let (.b, .some(unwrappedSomething)) = (esomeEnum, someString), unwrappedSomething.hasPrefix("so") {

} else if case (.a, .none) = (esomeEnum, someString) {

} else {

}

Upvotes: 1

OOPer
OOPer

Reputation: 47896

With a little modification to the Alexander's struct, you can write something like this:

struct PrefixMatcherWithHandler {
    var handler: (String)->Void
    var string: String
    init(_ string: String, handler: @escaping (String)->Void) {
        self.string = string
        self.handler = handler
    }

    static func ~= (prefix: String, matcher: PrefixMatcherWithHandler) -> Bool {
        if matcher.string.hasPrefix(prefix) {
            matcher.handler(prefix)
            return true
        } else {
            return false
        }
    }
}

var fullline: String = "Monet, Claude"

var artist: String? = nil

let matcher = PrefixMatcherWithHandler(fullline) {str in
    artist = str
}

switch matcher {
case "Monet":
    break
case "Cezanne":
    break
default: break
}
print(artist ?? "") //->Monet

But having some side-effect in boolean operators like ~= makes your code less readable and can easily make unexpected result.

If you just want to reduce some redundant reference to a same thing, switch-statement may not be a good tool for it.

For example, you can get the same result without defining specific matcher types:

var fullline: String = "Monet, Claude"

var artist: String? = nil

if let match = ["Monet", "Cezanne"].first(where: {fullline.hasPrefix($0)}) {
    artist = match
}
print(artist ?? "") //->Monet

ADDED for updated parts of the question

The following code behaves slightly different than prefix-matching, but I believe you do not want to match "Mon" to the line Monet, Claude - 1875, Snow in Argenteuil.

extension String {
    var firstWord: String? {
        var result: String? = nil
        enumerateSubstrings(in: startIndex..<endIndex, options: .byWords) {str, _, _, stop in
            result = str
            stop = true
        }
        return result
    }
}

func get_birthday(_ artist: String) {
    //What do you want to do?
    print(artist)
}

var fullline: String = "Monet, Claude - 1875, Snow in Argenteuil"

switch fullline.firstWord {
case let artist? where artist == "Monet":
    //code dedicated for "Monet"
    get_birthday(artist)
case let artist? where artist == "Cezanne":
    //code dedicated for "Cezanne"
    get_birthday(artist)
default:
    break
}

When you can retrieve data suitable for switch-statement, the code would be far more intuitive and readable.

Upvotes: 3

Alexander
Alexander

Reputation: 63281

You're giving that closure where a boolean is expected. Not sure why you would want to do this, but you could make it work by using () to invoke the closure.

var artist
switch fullline {
case let path where { () -> Bool in let artist = "Monet"; return path.hasPrefix(artist) }():
    artist = "Monet"
case let path where path.hasPrefix("Cezanne"):
    artist = "Cezanne"
default: ()
}

Here is how I would do this:

import Foundation

struct PrefixMatcher {
    let string: String
    init(_ string: String) { self.string = string }

    static func ~= (prefix: String, matcher: PrefixMatcher) -> Bool {
        return matcher.string.hasPrefix(prefix)
    }
}

extension String {
    var prefix: PrefixMatcher { return PrefixMatcher(self) }
}


let fullline = "Monet 123456789"

let artist: String?

switch fullline.prefix {
    case "Monet": artist = "Monet"
    case "Cezanne": artist = "Cezanne"
    default: artist = nil
}

print(artist as Any)

More general solution:

import Foundation

struct PredicateMatcher<Pattern> {
    typealias Predicate = (Pattern) -> Bool

    let predicate: Predicate

    static func ~=(pattern: Pattern,
                   matcher: PredicateMatcher<Pattern>) -> Bool {
        return matcher.predicate(pattern)
    }
}

extension String {
    var prefix: PredicateMatcher<String> {
        return PredicateMatcher(predicate: self.hasPrefix)
    }
}

Upvotes: 2

Related Questions