如何计算 Swift 数组中元素的出现次数?

我已经看到了一些这样的例子,但是所有这些似乎都依赖于知道你想要计算出现的元素。我的数组是动态生成的,所以我没有办法知道我想要计算哪个元素的出现次数(我想要计算所有元素的出现次数)。有人能给点建议吗?

先谢谢你

编辑:

也许我应该说得更清楚一点,数组将包含多个不同的字符串(例如。 ["FOO", "FOO", "BAR", "FOOBAR"]

我怎样才能数出现 foo,bar 和 foobar 而不事先知道它们是什么?

92792 次浏览

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]

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.

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)]

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

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)
array.filter{$0 == element}.count

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)

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:

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]

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]
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]
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]

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)

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)
}
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)")
}

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