106

I'm building an iOS app with swift and i need to get all unique values of array of strings.

I've been reading the apple developer docs but it doesn't seem to have a function for it.

Can someone give me an hint?

user3806505
  • 1,143
  • 2
  • 7
  • 8

4 Answers4

209

There might be a more efficient way, but an extension would probably be most straightforward:

extension Array where Element: Equatable {
    var unique: [Element] {
        var uniqueValues: [Element] = []
        forEach { item in
            guard !uniqueValues.contains(item) else { return }
            uniqueValues.append(item)
        }
        return uniqueValues
    }
}

If order doesn't matter and objects are also hashable:

let array = ["one", "one", "two", "two", "three", "three"]
// order NOT guaranteed
let unique = Array(Set(array))
// ["three", "one", "two"]
Logan
  • 52,262
  • 20
  • 99
  • 128
75

There isn't a function to do this in the Swift standard library, but you could write one:

extension Sequence where Iterator.Element: Hashable {
    func unique() -> [Iterator.Element] {
        var seen: [Iterator.Element: Bool] = [:]
        return self.filter { seen.updateValue(true, forKey: $0) == nil }
    }
}

let a = ["four","one", "two", "one", "three","four", "four"]
a.unique // ["four", "one", "two", "three"]

This has the downside of requiring the contents of the sequence to be hashable, not just equatable, but then again most equatable things are, including strings.

It also preserves the original ordering unlike, say, putting the contents in a dictionary or set and then getting them back out again.

tobiasdm
  • 9,688
  • 1
  • 18
  • 15
Airspeed Velocity
  • 40,491
  • 8
  • 113
  • 118
4

I don't know of a built in way. This generic function would do it:

func distinct<S: SequenceType, E: Equatable where E==S.Generator.Element>(source: S) -> [E]
{
    var unique = [E]()

    for item in source
    {
        if !contains(unique, item)
        {
            unique.append(item)
        }
    }
    return unique
}

The downside here is that this solution runs in O(n2).

Ben Kane
  • 9,331
  • 6
  • 36
  • 58
  • `contains` runs in O(n) so this solution runs in quadratic time (though it does have the benefit of not requiring the elements be hashable). – Airspeed Velocity Dec 23 '14 at 16:47
  • @AirspeedVelocity Yes that's a good point. And being hashable won't normally be a problem like you've pointed out in your solution. Nonetheless it is an alternate, albeit slower, solution if having to be hashable is a game breaker. I prefer your solution but I think I'm going to keep this posted for that reason. – Ben Kane Dec 23 '14 at 16:53
  • Overloading means you can implement both, and the best one will be picked! (because Hashable conforms to Equatable, so is more specific, overload resolution will prefer it :) – Airspeed Velocity Dec 23 '14 at 16:55
  • (so long as you make both versions either take a sequence or an array, that is... otherwise they'll fight) – Airspeed Velocity Dec 23 '14 at 16:58
  • Yep! Gotta love Swift :) I'll edit my answer to take a sequence to be more generic and in case anyone wants to take the overloading approach. – Ben Kane Dec 23 '14 at 17:27
1

Use a dictionary like var unique = [<yourtype>:Bool]() and fill in the values like unique[<array value>] = true in a loop. Now unique.keys has what you need.

qwerty_so
  • 35,448
  • 8
  • 62
  • 86