Histogram of Array in Swift

◇◆丶佛笑我妖孽 提交于 2019-12-23 17:01:09

问题


I'm trying to write a generic histogram function that operates on an Array, but I'm running into difficulties as Type 'Element' does not conform to protocol 'Hashable'.

extension Array {
    func histogram() -> [Array.Element: Int] {
        return self.reduce([Array.Element: Int]()) { (acc, key) in
                let value = (acc[key] == nil) ? 1 : (acc[key]! + 1)
                return acc.dictionaryByUpdatingKey(key: key, value: value)
        }
    }
}

where dictionaryByUpdatingKey(...) mutates an existing dictionary as follows:

extension Dictionary {
    func dictionaryByUpdatingKey(key: Dictionary.Key, value: Dictionary.Value) -> Dictionary {
        var mutableSelf = self
        let _ = mutableSelf.updateValue(value, forKey: key)
        return mutableSelf
    }
}

I have tried replacing Array.Element with AnyHashable and then forcing the key as! AnyHashable, but this seems messy and the return type should preferably be of the same type as the Array.Element and not of AnyHashable.

I wish to use the Array extension as follows:

let names = ["Alex", "Alex", "James"]
print(names.histogram()) // ["James": 1, "Alex": 2]

or

let numbers = [2.0, 2.0, 3.0]
print(numbers.histogram()) // [3.0: 1, 2.0: 2]

回答1:


Add the generic where clause: where Element: Hashable to your extension:

extension Array where Element: Hashable {
    func histogram() -> [Array.Element: Int] {
        return self.reduce([Array.Element: Int]()) { (acc, key) in
            let value = acc[key, default: 0] + 1
            return acc.dictionaryByUpdatingKey(key: key, value: value)
        }
    }
}

I also incorporated @MartinR's suggestion of using the new default value for dictionary look ups.


Using reduce(into:_:) you can do this much more simply and efficiently:

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



回答2:


First, you could limit the Element type to only be Hashable.

extension Array where Array.Element:Hashable {

After this, you might get another error because the swift compiler is a little "overstrained". Try to give him a hint:

typealias RT = [Array.Element: Int]

and use it everywhere. So:

extension Array where Array.Element:Hashable {
    typealias RT = [Array.Element: Int]
    func histogram() -> RT {
        return self.reduce(RT()) { (acc, key) in
            let value = (acc[key] == nil) ? 1 : (acc[key]! + 1)
            return acc.dictionaryByUpdatingKey(key: key, value: value)
        }
    }
}

finally should work.



来源:https://stackoverflow.com/questions/49979152/histogram-of-array-in-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!