How to filter unique items of an array in Swift

Updated Jan 11, 2024#swift#ios#snippets

If you have an array with repeated elements, finding the unique items involves identifying and extracting only the elements that appear once, discarding any duplicates.

In Swift, an array is a collection of values that can be of any type. Sometimes, you may want to filter only the unique items of an array. There are different approaches you can take. If you don’t care about the order, you can use a Set or Dictionary to remove any duplicates, and then convert it back to an array.

If order is important, use array or reduce method approach.

Using a Set

A set in Swift is a collection of unique values of the same type that are unordered. You don’t specify or remember the order in which elements are added, iterating over a set might not give you elements in the same order as you inserted them.

All elements in a set must be of the same data type, like integers, strings, or custom types that conform to the Hashable protocol.

extension Array where Element: Hashable {
  func unique() -> [Element] {
    Array(Set(self))
  }
}

let strings = ["one", "one", "two", "three", "three"]
print(strings.unique())
// ["one", "three", "two"]

let numbers = [1, 1, 2, 2, 2, 3, 4, 4]
print(numbers.unique())
// [4, 2, 3, 1]

Using a Dictionary

A dictionary in Swift is a versatile collection type that stores key-value pairs. Each key acts as a unique identifier for its associated value, allowing you to quickly access and manage that information. Unlike arrays, dictionaries don’t maintain a specific order for their entries.

The key type in a dictionary must conform to the Hashable protocol. Each key in a dictionary must be unique to ensure that values can be retrieved unambiguously. There’s no restriction on value types.

Dictionaries inherently guarantee unique keys, making them suitable for filtering duplicates. The dictionary will use elements from the array as keys. The values can be anything, often a simple placeholder like true.

extension Array where Element: Hashable {
  func unique() -> [Element] {
    var dict = [Element: Bool]()
    self.forEach {
      dict[$0] = true
    }
    return Array(dict.keys)
  }
}

let strings = ["one", "one", "two", "three", "three"]
print(strings.unique())
// ["one", "three", "two"]

let numbers = [1, 1, 2, 2, 2, 3, 4, 4]
print(numbers.unique())
// [4, 2, 3, 1]

Using an Array

While straightforward, this approach has a time complexity of $O(n^2)$ due to nested array lookups. For large arrays, Set-based or Dictionary-based approaches are often more efficient.

extension Array where Element: Equatable {
  func unique() -> [Element] {
    var arr = [Element]()
    self.forEach {
      if !arr.contains($0) {
        arr.append($0)
      }
    }
    return arr
  }
}

let strings = ["one", "one", "two", "three", "three"]
print(strings.unique())
// ["one", "two", "three"]

let numbers = [1, 1, 2, 2, 2, 3, 4, 4]
print(numbers.unique())
// [1, 2, 3, 4]

Using reduce method

This technique employs the reduce method to process the array and build the unique array. The order of elements in the original array is generally preserved in the resulting unique array.

While more concise than the previous example, its time complexity is still $O(n^2)$ due to contains checks within reduce. For large arrays, Set-based or Dictionary-based approaches might be more efficient.

extension Array where Element: Equatable {
  func unique() -> [Element] {
    self.reduce([]) { result, element in
      result.contains(element) ? result : result + [element]
    }
  }
}

let strings = ["one", "one", "two", "three", "three"]
print(strings.unique())
// ["one", "two", "three"]

let numbers = [1, 1, 2, 2, 2, 3, 4, 4]
print(numbers.unique())
// [1, 2, 3, 4]

Remember to choose the method that best suits your specific requirements, considering factors like order preservation, performance, and code readability.