How to count occurrences of an element in a Swift array? How to count occurrences of an element in a Swift array? ios ios

How to count occurrences of an element in a Swift array?


Swift 3 and Swift 2:

You can use a dictionary of type [String: Int] to build up counts for each of the items in your [String]:

let arr = ["FOO", "FOO", "BAR", "FOOBAR"]var counts: [String: Int] = [:]for item in arr {    counts[item] = (counts[item] ?? 0) + 1}print(counts)  // "[BAR: 1, FOOBAR: 1, FOO: 2]"for (key, value) in counts {    print("\(key) occurs \(value) time(s)")}

output:

BAR occurs 1 time(s)FOOBAR occurs 1 time(s)FOO occurs 2 time(s)

Swift 4:

Swift 4 introduces (SE-0165) the ability to include a default value with a dictionary lookup, and the resulting value can be mutated with operations such as += and -=, so:

counts[item] = (counts[item] ?? 0) + 1

becomes:

counts[item, default: 0] += 1

That makes it easy to do the counting operation in one concise line using forEach:

let arr = ["FOO", "FOO", "BAR", "FOOBAR"]var counts: [String: Int] = [:]arr.forEach { counts[$0, default: 0] += 1 }print(counts)  // "["FOOBAR": 1, "FOO": 2, "BAR": 1]"

Swift 4: reduce(into:_:)

Swift 4 introduces a new version of reduce that uses an inout variable to accumulate the results. Using that, the creation of the counts truly becomes a single line:

let arr = ["FOO", "FOO", "BAR", "FOOBAR"]let counts = arr.reduce(into: [:]) { counts, word in counts[word, default: 0] += 1 }print(counts)  // ["BAR": 1, "FOOBAR": 1, "FOO": 2]

Or using the default parameters:

let counts = arr.reduce(into: [:]) { $0[$1, default: 0] += 1 }

Finally you can make this an extension of Sequence so that it can be called on any Sequence containing Hashable items including Array, ArraySlice, String, and String.SubSequence:

extension Sequence where Element: Hashable {    var histogram: [Element: Int] {        return self.reduce(into: [:]) { counts, elem in counts[elem, default: 0] += 1 }    }}

This idea was borrowed from this question although I changed it to a computed property. Thanks to @LeoDabus for the suggestion of extending Sequence instead of Array to pick up additional types.

Examples:

print("abacab".histogram)
["a": 3, "b": 2, "c": 1]
print("Hello World!".suffix(6).histogram)
["l": 1, "!": 1, "d": 1, "o": 1, "W": 1, "r": 1]
print([1,2,3,2,1].histogram)
[2: 2, 3: 1, 1: 2]
print([1,2,3,2,1,2,1,3,4,5].prefix(8).histogram)
[1: 3, 2: 3, 3: 2]
print(stride(from: 1, through: 10, by: 2).histogram)
[1: 1, 3: 1, 5: 1, 7: 1, 9: 1]


array.filter{$0 == element}.count


With Swift 5, according to your needs, you may choose one of the 7 following Playground sample codes to count the occurrences of hashable items in an array.


#1. Using Array's reduce(into:_:) and Dictionary's subscript(_:default:) subscript

let array = [4, 23, 97, 97, 97, 23]let dictionary = array.reduce(into: [:]) { counts, number in    counts[number, default: 0] += 1}print(dictionary) // [4: 1, 23: 2, 97: 3]

#2. Using repeatElement(_:count:) function, zip(_:_:) function and Dictionary's init(_:uniquingKeysWith:)initializer

let array = [4, 23, 97, 97, 97, 23]let repeated = repeatElement(1, count: array.count)//let repeated = Array(repeating: 1, count: array.count) // also workslet zipSequence = zip(array, repeated)let dictionary = Dictionary(zipSequence, uniquingKeysWith: { (current, new) in    return current + new})//let dictionary = Dictionary(zipSequence, uniquingKeysWith: +) // also worksprint(dictionary) // prints [4: 1, 23: 2, 97: 3]

#3. Using a Dictionary's init(grouping:by:) initializer and mapValues(_:) method

let array = [4, 23, 97, 97, 97, 23]let dictionary = Dictionary(grouping: array, by: { $0 })let newDictionary = dictionary.mapValues { (value: [Int]) in    return value.count}print(newDictionary) // prints: [97: 3, 23: 2, 4: 1]

#4. Using a Dictionary's init(grouping:by:) initializer and map(_:) method

let array = [4, 23, 97, 97, 97, 23]let dictionary = Dictionary(grouping: array, by: { $0 })let newArray = dictionary.map { (key: Int, value: [Int]) in    return (key, value.count)}print(newArray) // prints: [(4, 1), (23, 2), (97, 3)]

#5. Using a for loop and Dictionary's subscript(_:) subscript

extension Array where Element: Hashable {    func countForElements() -> [Element: Int] {        var counts = [Element: Int]()        for element in self {            counts[element] = (counts[element] ?? 0) + 1        }        return counts    }}let array = [4, 23, 97, 97, 97, 23]print(array.countForElements()) // prints [4: 1, 23: 2, 97: 3]

#6. Using NSCountedSet and NSEnumerator's map(_:) method (requires Foundation)

import Foundationextension Array where Element: Hashable {    func countForElements() -> [(Element, Int)] {        let countedSet = NSCountedSet(array: self)        let res = countedSet.objectEnumerator().map { (object: Any) -> (Element, Int) in            return (object as! Element, countedSet.count(for: object))        }        return res    }}let array = [4, 23, 97, 97, 97, 23]print(array.countForElements()) // prints [(97, 3), (4, 1), (23, 2)]

#7. Using NSCountedSet and AnyIterator (requires Foundation)

import Foundationextension Array where Element: Hashable {    func counForElements() -> Array<(Element, Int)> {        let countedSet = NSCountedSet(array: self)        var countedSetIterator = countedSet.objectEnumerator().makeIterator()        let anyIterator = AnyIterator<(Element, Int)> {            guard let element = countedSetIterator.next() as? Element else { return nil }            return (element, countedSet.count(for: element))        }        return Array<(Element, Int)>(anyIterator)    }}let array = [4, 23, 97, 97, 97, 23]print(array.counForElements()) // [(97, 3), (4, 1), (23, 2)]

Credits: