97

I've seen a few examples of this but all of those seem to rely on knowing which element you want to count the occurrences of. My array is generated dynamically so I have no way of knowing which element I want to count the occurrences of (I want to count the occurrences of all of them). Can anyone advise?

EDIT:

Perhaps I should have been clearer, the array will contain multiple different strings (e.g. ["FOO", "FOO", "BAR", "FOOBAR"]

How can I count the occurrences of foo, bar and foobar without knowing what they are in advance?

starball
  • 20,030
  • 7
  • 43
  • 238
Alex Chesters
  • 3,380
  • 5
  • 19
  • 27

16 Answers16

166

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]
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • 3
    Dictionary look ups return an optional value because the key might not exist in the dictionary. You need to unwrap that optional to use it. Here, I use the *nil coalescing operator* **??** which says unwrap the value if there is one, otherwise use the provided default `0`. The first time we encounter a string, it won't be in the `counts` dictionary yet, so `count["FOO"]` will return `nil` which **??** converts to `0`. The next time we encounter `"FOO"`, `count["FOO"]` will return `Optional(1)` which **??** will unwrap to `1`. – vacawama May 30 '15 at 12:23
  • what if I want to do with array of custom models ? – A.s.ALI Sep 24 '19 at 11:48
  • 1
    @A.s.ALI, make your model class/struct conform to `Hashable`. – vacawama Sep 24 '19 at 12:01
  • @A.s.ALI. Implement `hash(into:)` and `==`. For example, see [this answer](https://stackoverflow.com/a/57252984/1630618) – vacawama Sep 24 '19 at 12:03
  • can you please give me some help over here https://stackoverflow.com/questions/58074410/realm-subquery-to-filter-data-from-1-table-w-r-t-table-2 – A.s.ALI Sep 24 '19 at 12:43
  • @A.s.ALI, sorry I don’t know Realm. – vacawama Sep 24 '19 at 12:47
  • 1
    @vacawama It would be better to extend `Sequence` to support strings as well as subsequences. – Leo Dabus Jul 04 '20 at 20:57
  • Thanks @LeoDabus, I changed that and added some examples to show the variety of things you can `histogram`. – vacawama Jul 04 '20 at 21:44
  • Hey vacawama, would you be okay if I rearranged this answer so the most relevant sub-answer (using `reduce(into:_:)` with `subscript(_:default:)` comes first? – Alexander Jun 04 '21 at 18:54
  • @Alexander, hmm I'm not sure. Right now the answer reads and develops from top to bottom with assumptions that readers have read the material in that order. I can understand that it takes a while to get to the most relevant sub-answer. I suppose if we rearranged them and prefaced them with "Update 1" and "Update 2" that it could make sense that it develops from bottom to top and readers would know to look to the earlier material if they had questions. Did you have something like that in mind? – vacawama Jun 06 '21 at 02:01
  • Pretty much. Or you could use collapsible sections to be like: “here’s the most modern/idiomatic way… Are you curious how this is implemented under the hood? click here”, and you could show how the dictionary initializer could be written with a for loop – Alexander Jun 06 '21 at 02:19
160
array.filter{$0 == element}.count
Ruben
  • 1,950
  • 1
  • 12
  • 13
  • 14
    Because the question (at least in its current form) asks for getting the count for each element. This is fantastic for the count of one specific element. – Quinn Taylor Aug 21 '18 at 21:46
  • 6
    While this solution is extremely elegant, it can be inefficient because it creates a second array and then counts its elements. A more efficient solution would be something like `var count = 0 array.forEach { x in if x == element { count += 1 }}` – CristinaTheDev Nov 13 '18 at 22:34
  • Is this agnostic to what 'element' is? The OP's issue was that he does not know what the potential values might be – user1904273 Jan 16 '19 at 22:04
  • @user1904273 it's the element whose occurrences you want to count – CristinaTheDev Jan 17 '19 at 13:17
  • 4
    Why is this answer voted up? It doesn't answer the original question at all. – bojan Mar 14 '19 at 12:34
  • 1
    I wondered about the efficiency of using the `filter` method too before finding this discussion. So I tested it against @CristinaDeRito's `forEach` loop with a long array. For good measure, I also tested with a `for-in` loop. Time to complete each 1000 times: array.filter: 2.8 seconds; array.forEach: 1.7 seconds; for-in array: 0.7 seconds – Kal Jul 04 '19 at 06:35
  • We should also be getting `count(where:)` for this very task in some future version of Swift. (It was approved and then removed from Swift 5.0.) – Kal Jul 05 '19 at 03:44
  • @Kal The issue was performance at compile time. You can just rename it if you would like to. `extension Sequence { func frequency(where predicate: (Element) -> Bool) -> Int { reduce(0) { predicate($1) ? $0 + 1 : $0 } } }` – Leo Dabus Sep 08 '21 at 14:49
  • 1
    @bojan it's counted up because it's what we were all looking for when we came here – Paul Bruneau Dec 07 '22 at 17:13
55

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 works

let zipSequence = zip(array, repeated)

let dictionary = Dictionary(zipSequence, uniquingKeysWith: { (current, new) in
    return current + new
})
//let dictionary = Dictionary(zipSequence, uniquingKeysWith: +) // also works

print(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 Foundation

extension 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 Foundation

extension 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:

Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
11

I updated oisdk's answer to Swift2.

16/04/14 I updated this code to Swift2.2

16/10/11 updated to Swift3


Hashable:

extension Sequence where Self.Iterator.Element: Hashable {
    private typealias Element = Self.Iterator.Element

    func freq() -> [Element: Int] {
        return reduce([:]) { (accu: [Element: Int], element) in
            var accu = accu
            accu[element] = accu[element]?.advanced(by: 1) ?? 1
            return accu
        }
    }
}

Equatable:

extension Sequence where Self.Iterator.Element: Equatable {
    private typealias Element = Self.Iterator.Element

    func freqTuple() -> [(element: Element, count: Int)] {

        let empty: [(Element, Int)] = []

        return reduce(empty) { (accu: [(Element, Int)], element) in
            var accu = accu
            for (index, value) in accu.enumerated() {
                if value.0 == element {
                    accu[index].1 += 1
                    return accu
                }
            }

            return accu + [(element, 1)]
        }
    }
}

Usage

let arr = ["a", "a", "a", "a", "b", "b", "c"]
print(arr.freq()) // ["b": 2, "a": 4, "c": 1]
print(arr.freqTuple()) // [("a", 4), ("b", 2), ("c", 1)]

for (k, v) in arr.freq() {
    print("\(k) -> \(v) time(s)")
}
// b -> 2 time(s)
// a -> 4 time(s)
// c -> 1 time(s)

for (element, count) in arr.freqTuple() {
    print("\(element) -> \(count) time(s)")
}
// a -> 4 time(s)
// b -> 2 time(s)
// c -> 1 time(s)
Community
  • 1
  • 1
ken0nek
  • 517
  • 5
  • 9
  • 1
    A nice solution - and one I borrowed - but unfortunately the Swift syntax is being changed soon (for v.3) to disallow `var` in function parameters. I've tried fixing it, but have had no luck so far... – MassivePenguin Mar 29 '16 at 20:23
  • 1
    @MassivePenguin Thanks! I updated this answer to Swift2.2. Please use this :) – ken0nek Apr 14 '16 at 08:50
4

Use an NSCountedSet. In Objective-C:

NSCountedSet* countedSet = [[NSCountedSet alloc] initWithArray:array];
for (NSString* string in countedSet)
    NSLog (@"String %@ occurs %zd times", string, [countedSet countForObject:string]);

I assume that you can translate this into Swift yourself.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
4

How about:

func freq<S: SequenceType where S.Generator.Element: Hashable>(seq: S) -> [S.Generator.Element:Int] {

  return reduce(seq, [:]) {

    (var accu: [S.Generator.Element:Int], element) in
    accu[element] = accu[element]?.successor() ?? 1
    return accu

  }
}

freq(["FOO", "FOO", "BAR", "FOOBAR"]) // ["BAR": 1, "FOOBAR": 1, "FOO": 2]

It's generic, so it'll work with whatever your element is, as long as it's hashable:

freq([1, 1, 1, 2, 3, 3]) // [2: 1, 3: 2, 1: 3]

freq([true, true, true, false, true]) // [false: 1, true: 4]

And, if you can't make your elements hashable, you could do it with tuples:

func freq<S: SequenceType where S.Generator.Element: Equatable>(seq: S) -> [(S.Generator.Element, Int)] {

  let empty: [(S.Generator.Element, Int)] = []

  return reduce(seq, empty) {

    (var accu: [(S.Generator.Element,Int)], element) in

    for (index, value) in enumerate(accu) {
      if value.0 == element {
        accu[index].1++
        return accu
      }
    }

    return accu + [(element, 1)]

  }
}

freq(["a", "a", "a", "b", "b"]) // [("a", 3), ("b", 2)]
oisdk
  • 9,763
  • 4
  • 18
  • 36
4

I like to avoid inner loops and use .map as much as possible. So if we have an array of string, we can do the following to count the occurrences

var occurances = ["tuples", "are", "awesome", "tuples", "are", "cool", "tuples", "tuples", "tuples", "shades"]

var dict:[String:Int] = [:]

occurances.map{
    if let val: Int = dict[$0]  {
        dict[$0] = val+1
    } else {
        dict[$0] = 1
    }
}

prints

["tuples": 5, "awesome": 1, "are": 2, "cool": 1, "shades": 1]
EmilDo
  • 1,177
  • 3
  • 16
  • 33
4

Swift 4

let array = ["FOO", "FOO", "BAR", "FOOBAR"]

// Merging keys with closure for conflicts
let mergedKeysAndValues = Dictionary(zip(array, repeatElement(1, count: array.count)), uniquingKeysWith: +) 

// mergedKeysAndValues is ["FOO": 2, "BAR": 1, "FOOBAR": 1]
eemrah
  • 1,603
  • 3
  • 19
  • 37
ViciV
  • 303
  • 2
  • 11
2

An other approach would be to use the filter method. I find that the most elegant

var numberOfOccurenses = countedItems.filter(
{
    if $0 == "FOO" || $0 == "BAR" || $0 == "FOOBAR"  {
        return true
    }else{
        return false
    }
}).count
Lars Christoffersen
  • 1,719
  • 13
  • 24
2

You can use this function to count the occurence of the items in array

func checkItemCount(arr: [String]) {       
    var dict = [String: Any]()

    for x in arr {  
        var count = 0 
        for y in arr {
            if y == x {
                count += 1
            }
        }

        dict[x] = count
    }

    print(dict)
}

You can implement it like this -

let arr = ["FOO", "FOO", "BAR", "FOOBAR"]
checkItemCount(arr: arr)
koen
  • 5,383
  • 7
  • 50
  • 89
1
public extension Sequence {

    public func countBy<U : Hashable>(_ keyFunc: (Iterator.Element) -> U) -> [U: Int] {

    var dict: [U: Int] = [:]
    for el in self {
        let key = keyFunc(el)
        if dict[key] == nil {
            dict[key] = 1
        } else {
            dict[key] = dict[key]! + 1
        }

        //if case nil = dict[key]?.append(el) { dict[key] = [el] }
    }
    return dict
}


let count = ["a","b","c","a"].countBy{ $0 }
// ["b": 1, "a": 2, "c": 1]


struct Objc {
    var id: String = ""

}

let count = [Objc(id: "1"), Objc(id: "1"), Objc(id: "2"),Objc(id: "3")].countBy{ $0.id }

// ["2": 1, "1": 2, "3": 1]
Carlos Chaguendo
  • 2,895
  • 19
  • 24
1
extension Collection where Iterator.Element: Comparable & Hashable {
    func occurrencesOfElements() -> [Element: Int] {
        var counts: [Element: Int] = [:]
        let sortedArr = self.sorted(by: { $0 > $1 })
        let uniqueArr = Set(sortedArr)
        if uniqueArr.count < sortedArr.count {
            sortedArr.forEach {
                counts[$0, default: 0] += 1
            }
        }
        return counts
    }
}

// Testing with...
[6, 7, 4, 5, 6, 0, 6].occurrencesOfElements()

// Expected result (see number 6 occurs three times) :
// [7: 1, 4: 1, 5: 1, 6: 3, 0: 1]
iKK
  • 6,394
  • 10
  • 58
  • 131
0

First Step in Counting Sort.

var inputList = [9,8,5,6,4,2,2,1,1]
var countList : [Int] = []

var max = inputList.maxElement()!

// Iniate an array with specific Size and with intial value.
// We made the Size to max+1 to integrate the Zero. We intiated the array with Zeros because it's Counting.

var countArray = [Int](count: Int(max + 1), repeatedValue: 0)

for num in inputList{
    countArray[num] += 1
}

print(countArray)
Atef
  • 2,872
  • 1
  • 36
  • 32
0

Two Solutions:

  1. Using forEach loop
let array = [10,20,10,40,10,20,30]
var processedElements = [Int]()
array.forEach({
    let element = $0
    
    // Check wether element is processed or not
    guard processedElements.contains(element) == false else {
        return
    }
    let elementCount = array.filter({ $0 == element}).count
    print("Element: \(element): Count \(elementCount)")
    
    // Add Elements to already Processed Elements
    processedElements.append(element)
})
  1. Using Recursive Function
let array = [10,20,10,40,10,20,30]
self.printElementsCount(array: array)

func printElementsCount(array: [Int]) {
    guard array.count > 0 else {
        return
    }
    let firstElement = array[0]
    let filteredArray = array.filter({ $0 != firstElement })
    print("Element: \(firstElement): Count \(array.count - filteredArray.count )")
    printElementsCount(array: filteredArray)
}
rohit phogat
  • 137
  • 1
  • 10
0
import Foundation

var myArray:[Int] = []

for _ in stride(from: 0, to: 10, by: 1) {
    myArray.append(Int.random(in: 1..<6))
}

// Method 1:
var myUniqueElements = Set(myArray)

print("Array: \(myArray)")
print("Unique Elements: \(myUniqueElements)")

for uniqueElement in myUniqueElements {

    var quantity = 0

    for element in myArray {

        if element == uniqueElement {
            quantity += 1
        }
    }
    print("Element: \(uniqueElement), Quantity: \(quantity)")
}

// Method 2:
var myDict:[Int:Int] = [:]

for element in myArray {
    myDict[element] = (myDict[element] ?? 0) + 1
}
print(myArray)

for keyValue in myDict {
    print("Element: \(keyValue.key), Quantity: \(keyValue.value)")
}
Rehan Ali Khan
  • 527
  • 10
  • 23
0

The structure which do the count

struct OccureCounter<Item: Hashable> {
    var dictionary = [Item: Int]()

    mutating func countHere(_ c: [Item]) {
        c.forEach { add(item: $0) }
        printCounts()
    }

    mutating func add(item: Item) {
        if let value = dictionary[item] {
            dictionary[item] = value + 1
        } else {
            dictionary[item] = 1
        }
    }
    func printCounts() {
        print("::: START")
        dictionary
            .sorted { $0.value > $1.value }
            .forEach { print("::: \($0.value) — \($0.key)") }
    
        let all = dictionary.reduce(into: 0) { $0 += $1.value }
        print("::: ALL: \(all)")
        print("::: END")
    }
}

Usage

struct OccureTest {
    func test() {
        let z: [Character] = ["a", "a", "b", "a", "b", "c", "d", "e", "f"]
        var counter = OccureCounter<Character>()
        counter.countHere(z)
    }
}

It prints:

::: START
::: 3 — a
::: 2 — b
::: 1 — c
::: 1 — f
::: 1 — e
::: 1 — d
::: ALL: 9
::: END
scherv
  • 11
  • 3