Reputation: 6714
Say I have an object:
struct Foo {
let id: Int
let bar: Int
}
Now I have 5 of these objects in an array:
let foo1 = Foo(id: 1, bar: 1)
let foo2 = Foo(id: 2, bar: 1)
let foo3 = Foo(id: 3, bar: 2)
let foo4 = Foo(id: 4, bar: 3)
let foo5 = Foo(id: 5, bar: 3)
let fooArray = [foo1, foo2, foo3, foo4, foo5]
What would be a clean way of filtering foo
objects that have unique bar
values?
// Desired output
let filteredArray = [foo1, foo3, foo4]
Assume there's anywhere from a few hundred to a few thousand objects to iterate upon.
Upvotes: 3
Views: 2740
Reputation: 33979
I'm not a fan of the accepted answer. Having the set outside the closure that uses it doesn't seem appropriate. I would rather keep everything contained. There's a CS term that applies but I don't remember what it is...
I would rather see this:
let uniquedBars = fooArray
.reduce(into: (result: [Foo](), set: Set<Int>())) { partial, next in
if partial.set.insert(next.bar).inserted {
partial.result.append(next)
}
}
.result
Upvotes: 0
Reputation: 540075
One possible approach is to use a Set
which keeps track of which
bar
values have already been seen:
var seenBarValues = Set<Int>()
let filteredArray = fooArray.filter { foo in
if seenBarValues.contains(foo.bar) {
// We already had a `Foo` with this `bar` value: skip.
return false
} else {
// First `Foo` with this `bar` value: remember and include.
seenBarValues.insert(foo.bar)
return true
}
}
As @Hamish correctly pointed out, this can be shortened to
var seenBarValues = Set<Int>()
let filteredArray = fooArray.filter {
seenBarValues.insert($0.bar).inserted
}
using the fact that
public mutating func insert(_ newMember: Element) -> (inserted: Bool, memberAfterInsert: Element)
returns a tuple whose first member indicates if an element equal to the newly inserted one was already present in the set.
Upvotes: 5