J'ai vu quelques exemples de cela, mais tous semblent vouloir savoir de quel élément vous voulez compter les occurrences. Mon tableau est généré dynamiquement, je n'ai donc aucun moyen de savoir de quel élément je veux compter les occurrences (je veux compter toutes les occurrences). Quelqu'un peut-il conseiller?
Merci d'avance
MODIFIER:
J'aurais peut-être dû être plus clair, le tableau contiendra plusieurs chaînes différentes (par exemple ["FOO", "FOO", "BAR", "FOOBAR"]
Comment puis-je compter les occurrences de foo, bar et foobar sans savoir ce qu'elles sont en avance?
Swift 3 et Swift 2:
Vous pouvez utiliser un dictionnaire de type [String: Int]
pour créer des comptes pour chacun des éléments de votre [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)")
}
sortie:
BAR occurs 1 time(s)
FOOBAR occurs 1 time(s)
FOO occurs 2 time(s)
Swift 4:
Swift 4 introduit (SE-0165) la possibilité d'inclure une valeur par défaut avec une recherche dans le dictionnaire. La valeur résultante peut être mutée avec des opérations telles que +=
et -=
, afin:
counts[item] = (counts[item] ?? 0) + 1
devient:
counts[item, default: 0] += 1
Cela facilite l'opération de comptage dans une ligne concise en utilisant 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 introduit une nouvelle version de reduce
qui utilise une variable inout
pour accumuler les résultats. En utilisant cela, la création des comptes devient vraiment une seule ligne:
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]
Ou en utilisant les paramètres par défaut:
let counts = arr.reduce(into: [:]) { $0[$1, default: 0] += 1 }
Enfin, vous pouvez en faire une extension de Array
afin qu’elle puisse être appelée sur n’importe quel tableau contenant des éléments Hashable
:
extension Array where Element: Hashable {
var histogram: [Element: Int] {
return self.reduce(into: [:]) { counts, elem in counts[elem, default: 0] += 1 }
}
}
Cette idée a été empruntée à cette question bien que je l’aie changée en une propriété calculée.
array.filter{$0 == element}.count
Avec Swift 4, en fonction de vos besoins, vous pouvez choisir l’un des 6 codes de terrain de jeu suivants pour compter le nombre d’éléments pouvant être séparés dans un tableau.
Array
reduce(into:_:)
et Dictionary
subscript(_:default:)
(nécessite Swift 4)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]
repeatElement(_:count:)
, de la fonction Zip(_:_:)
, de Dictionary
init(_:uniquingKeysWith:)
initializer et de renvoyer une Dictionary
(requiert Swift 4)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]
Dictionary
init(grouping:by:)
et renvoi d'une Array
de n-uplets (requiert Swift 4)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)]
Dictionary
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]
NSCountedSet
, map
et renvoi d'une Array
de n-uplets (nécessite 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)]
NSCountedSet
, AnyIterator
et renvoyer une Array
de n-uplets (nécessite 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)]
Crédits:
J'ai mis à jour la réponse de oisdk à Swift2.
16/04/14 J'ai mis à jour ce code vers Swift2.2
16/10/11 mis à jour à 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
}
}
}
Équitable:
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)
Que diriez-vous:
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]
C'est générique, donc ça fonctionnera avec n'importe quel élément, tant qu'il est hashable:
freq([1, 1, 1, 2, 3, 3]) // [2: 1, 3: 2, 1: 3]
freq([true, true, true, false, true]) // [false: 1, true: 4]
Et, si vous ne pouvez pas rendre vos éléments hashable, vous pouvez le faire avec des n-uplets:
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)]
J'aime éviter les boucles internes et utiliser .map autant que possible ... Donc, si nous avons un tableau de chaînes, nous pouvons procéder comme suit pour compter les 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
}
}
empreintes
["tuples": 5, "awesome": 1, "are": 2, "cool": 1, "shades": 1]
Utilisez un NSCountedSet. En Objective-C:
NSCountedSet* countedSet = [[NSCountedSet alloc] initWithArray:array];
for (NSString* string in countedSet)
NSLog (@"String %@ occurs %zd times", string, [countedSet countForObject:string]);
Je suppose que vous pouvez traduire cela vous-même en Swift.
Une autre approche consisterait à utiliser la méthode du filtre. Je trouve que le plus élégant
var numberOfOccurenses = countedItems.filter(
{
if $0 == "FOO" || $0 == "BAR" || $0 == "FOOBAR" {
return true
}else{
return false
}
}).count
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]
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]
Swift 4
let array = ["FOO", "FOO", "BAR", "FOOBAR"]
// Merging keys with closure for conflicts
let mergedKeysAndValues = Dictionary(Zip(array, repeatElement(1, count: array)), uniquingKeysWith: +)
// mergedKeysAndValues is ["FOO": 2, "BAR": 1, "FOOBAR": 1]