-
-
Save chockenberry/d6c08e9916442d11a0c69fb01454c063 to your computer and use it in GitHub Desktop.
import UIKit | |
enum Name { | |
case foo(Foo) | |
case bar(Bar) | |
struct Foo { | |
let name: String | |
} | |
struct Bar { | |
let count: Int | |
} | |
} | |
let names = [Name.foo(Name.Foo(name: "hello")), Name.bar(Name.Bar(count: 123)), Name.bar(Name.Bar(count: 999))] | |
// The goal: to get the first Name.Bar item from names, and more importantly, to make the code readable. | |
func firstBar_TakeOne(in names: [Name]) -> Name.Bar? { | |
if case let .bar(bar) = names.first(where: { name in | |
if case .bar(_) = name { | |
return true | |
} | |
return false | |
}) { | |
return bar | |
} | |
return nil | |
} | |
func firstBar_TakeTwo(in names: [Name]) -> Name.Bar? { | |
let initialResult: Name.Bar? = nil | |
let result = names.reduce(into: initialResult) { result, name in | |
guard result == nil else { return } | |
if case let .bar(bar) = name { | |
result = bar | |
} | |
} | |
return result | |
} | |
if let bar = firstBar_TakeOne(in: names) { | |
bar.count | |
} | |
if let bar = firstBar_TakeTwo(in: names) { | |
bar.count | |
} |
I would personally go with:
if let bar = names.extractFirst({ // from answer above
guard case let .bar(barVal) = $0 else { // fold all other cases into:
return nil;
}
return barVal;
}) {
bar.count;
}
because I assume struct Name.Bar's role is nailed down enough; if it isn't, what Jared Sinclair said.
Building on the for-loop solution from @ezfe: you can make the variable binding in the loop condition:
func firstBar(in names: [Name]) -> Name.Bar? {
for case .bar(let bar) in names {
return bar
}
return nil
}
This will iterate over names
until it finds the first .bar(_)
value and return its associated value (bound to bar
in the loop condition). If names
doesn't contain any .bar(_)
values it will return nil.
I would tend to reach for something like this:
let firstBar = names
.lazy
.compactMap({
switch $0 {
case let .bar(bar): bar
default: nil
}
})
.first;
The combination of .lazy
and .first
should mean you only iterate as far through the collection as you need to, and the result is an Optional<Name.Bar>
.
If you use the asBar
or barValue
helper values others have contributed above, you could then write it as:
return names.lazy.compactMap(\.asBar).first
and still get nearly optimal performance. A regular for loop will probably be marginally faster for small collections though but not by much.
Watch out, the performance of that construct may surprise you: https://forums.swift.org/t/adding-firstas-to-sequence/36665/17
Ahhhh, overloads fun. π The first(where: { _ in true })
hack is terrible, too π but if abstracted over and provided with a very good explanatory comment, I guess I could live with it. π
I have often found that the key to readable code is to employ tactics like (A) add computed properties to model types that make code more expressive and (B) write reusable extension methods on Swift Standard Library types that you could argue should exist already in well-known forms.
In the following:
barValue
computed property to NameextractFirst(body:)
utility method to CollectionThe
extension Collection
methods are best defined in a reusable location, the kind of in-house package that ends up getting imported by every project because of the gaps it patches in the standard library.