【问题标题】:How to count occurrences of an element in a Swift array?如何计算 Swift 数组中元素的出现次数?
【发布时间】:2015-08-13 06:15:50
【问题描述】:

我见过一些这样的例子,但所有这些似乎都依赖于知道你想要计算哪个元素的出现次数。我的数组是动态生成的,所以我无法知道要计算哪个元素的出现次数(我想计算所有元素的出现次数)。谁能给点建议?

提前致谢

编辑:

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

如果事先不知道 foo、bar 和 foobar 是什么,如何计算它们的出现次数?

【问题讨论】:

  • 不要将 Swift 数组与 NSArray 混淆。这些不一样。

标签: ios arrays swift nsarray


【解决方案1】:

Swift 3 和 Swift 2:

您可以使用[String: Int] 类型的字典来为您的[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)")
}

输出:

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

斯威夫特 4:

Swift 4 introduces (SE-0165) 能够在字典查找中包含默认值,并且结果值可以通过 +=-= 等操作进行变异,因此:

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

变成:

counts[item, default: 0] += 1

这使得使用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 引入了一个新版本的reduce,它使用inout 变量来累积结果。使用它,计数的创建真正变成了一行:

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]

或者使用默认参数:

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

最后,您可以将其作为Sequence 的扩展,以便可以在任何包含Hashable 项目的Sequence 上调用它,包括ArrayArraySliceStringString.SubSequence

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

这个想法是从this question 借来的,虽然我把它改成了一个计算属性。感谢@LeoDabus 建议扩展Sequence 而不是Array 以获取更多类型。

示例:

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]

【讨论】:

  • 字典查找返回一个可选值,因为字典中可能不存在该键。您需要打开该选项才能使用它。在这里,我使用 nil 合并运算符 ?? 表示如果有值则解包,否则使用提供的默认值0。我们第一次遇到字符串时,它还没有出现在counts 字典中,所以count["FOO"] 将返回nil,而?? 将转换为0。下次遇到"FOO" 时,count["FOO"] 将返回Optional(1)?? 将解包到1
  • 如果我想处理自定义模型数组怎么办?
  • @A.s.ALI,让你的模型类/结构符合Hashable
  • @A.s.ALI。实现hash(into:)==。例如,请参阅this answer
  • @vacawama 最好扩展Sequence 以支持字符串和子序列。
【解决方案2】:
array.filter{$0 == element}.count

【讨论】:

  • 为什么人们不认为这是最好的答案完全超出了我的理解。
  • 迄今为止最简洁的答案
  • 因为问题(至少在当前形式中)要求获取每个元素的计数。这对于计算一个特定元素的数量来说非常棒。
  • 虽然这个解决方案非常优雅,但它可能效率低下,因为它会创建第二个数组然后计算其元素。更有效的解决方案是 var count = 0 array.forEach { x in if x == element { count += 1 }}
  • 为什么这个答案被投票?它根本没有回答最初的问题。
【解决方案3】:

使用 Swift 5,根据您的需要,您可以选择 7 以下 Playground 示例代码之一来计算数组中可散列项的出现次数。


#1。使用Arrayreduce(into:_:)Dictionarysubscript(_:default:) 下标

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。使用repeatElement(_:count:)函数、zip(_:_:)函数和Dictionaryinit(_: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。使用Dictionaryinit(grouping:by:) 初始化器和mapValues(_:) 方法

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。使用Dictionaryinit(grouping:by:) 初始化器和map(_:) 方法

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。使用 for 循环和 Dictionarysubscript(_:) 下标

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。使用NSCountedSetNSEnumeratormap(_:)方法(需要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。使用 NSCountedSetAnyIterator(需要 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)]

学分:

【讨论】:

  • 很棒的答案!
  • 全面而深思熟虑。赞一个!
【解决方案4】:

我将oisdk's answer 更新为 Swift2。

16/04/14 我将此代码更新为 Swift2.2

16/10/11 更新到 Swift3


可散列:

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

平等的:

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

用法

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)

【讨论】:

  • 一个不错的解决方案 - 我借用了一个 - 但不幸的是,Swift 语法即将更改(对于 v.3)以禁止在函数参数中使用 var。我已经尝试修复它,但到目前为止还没有运气......
  • @MassivePenguin 谢谢!我将此答案更新为 Swift2.2。请使用这个:)
【解决方案5】:

使用 NSCountedSet。在 Objective-C 中:

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

我假设您可以自己将其翻译成 Swift。

【讨论】:

    【解决方案6】:

    怎么样:

    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]
    

    它是通用的,所以它适用于你的任何元素,只要它是可散列的:

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

    而且,如果你不能让你的元素可散列,你可以用元组来做到这一点:

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

    【讨论】:

      【解决方案7】:

      我喜欢避免内部循环并尽可能使用 .map。 因此,如果我们有一个字符串数组,我们可以执行以下操作来计算出现次数

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

      打印

      ["tuples": 5, "awesome": 1, "are": 2, "cool": 1, "shades": 1]
      

      【讨论】:

        【解决方案8】:

        斯威夫特 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]
        

        【讨论】:

          【解决方案9】:

          另一种方法是使用过滤器方法。我觉得最优雅的

          var numberOfOccurenses = countedItems.filter(
          {
              if $0 == "FOO" || $0 == "BAR" || $0 == "FOOBAR"  {
                  return true
              }else{
                  return false
              }
          }).count
          

          【讨论】:

            【解决方案10】:
            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]
            

            【讨论】:

              【解决方案11】:
              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]
              

              【讨论】:

                【解决方案12】:

                您可以使用此函数来计算数组中项目的出现次数

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

                你可以这样实现——

                let arr = ["FOO", "FOO", "BAR", "FOOBAR"]
                checkItemCount(arr: arr)
                

                【讨论】:

                  【解决方案13】:

                  计数排序的第一步。

                  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)
                  

                  【讨论】:

                    【解决方案14】:

                    两种解决方案:

                    1. 使用forEach 循环
                    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. 使用递归函数
                    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)
                    }
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2022-11-30
                      • 1970-01-01
                      • 2014-09-30
                      • 1970-01-01
                      • 2021-12-09
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多