web-dev-qa-db-fra.com

Comment compter les occurrences d'un élément dans un tableau Swift?

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?

39
Alex Chesters

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.

83
vacawama
array.filter{$0 == element}.count
61
Ruben

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.


#1. Utilisation de Arrayreduce(into:_:) et Dictionarysubscript(_: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]

# 2. Utilisation de la fonction repeatElement(_:count:), de la fonction Zip(_:_:), de Dictionaryinit(_: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]

# 3. Utilisation d'un initialiseur Dictionaryinit(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)]

# 4. Utiliser une boucle for et renvoyer une 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]

# 5. Utilisation de la méthode 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)]

# 6. Utiliser 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:

20
Imanou Petit

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)
9
ken0nek

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

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

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. 

3
gnasher729

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
1
user1700737
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]
0
iKK
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]
0

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