Reputation: 3058
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]
Upvotes: 5
Views: 1971
Reputation: 154691
Add the generic where clause: where Element: Hashable
to your extension:
extension Sequence where Element: Hashable {
func histogram() -> [Element: Int] {
return self.reduce([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 Sequence where Element: Hashable {
func histogram() -> [Element: Int] {
return self.reduce(into: [:]) { counts, elem in counts[elem, default: 0] += 1 }
}
}
Upvotes: 12
Reputation: 10209
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.
Upvotes: 1