Reputation: 2826
I have a dictionary like this:
let tempDict = [100:2, 101:3, 102:4]
I need to find the weighted average of all the elements in the dictionary. So I essentially want to do this
let keyMultValues = 100*2+101*3+102*4
let valTotal = 2+3+4
let final = keyMultValues/valTotal
I know I can loop through all the items in a dictionary but I thought maybe there is a more efficient and cleaner way to do this.
Is there a better way than this?
var keyMultValues = 0.0
tempDict.forEach({
keyMultValues += $0.key*$0.value
})
let valTotal = reduce(priceDict.values, 0, +) //this part isn't working properly so any help you can give here would be great. I am using swift 4
Upvotes: 1
Views: 275
Reputation: 53010
You started with:
var keyMultValues = 0
tempDict.forEach({
keyMultValues += $0.key*$0.value
})
let valTotal = reduce(priceDict.values, 0, +) //this part isn't working properly so any help you can give here would be great. I am using swift 4
and ask:
Is there a better way than this?
Well your error is you've written the reduce
incorrectly, it should have been:
tempDict.values.reduce(0, +)
and with this fix your code produces the correct (using integer arithmetic) result.
However your code does two passes over the data, the foreach
and the reduce
(and the other answers at the time of writing do two or three). Your own solution shows you only need to do one pass, just modify it to follow the same pattern for valTotal
as you used for keyMultValues
:
var keyMultValues = 0
var valTotal = 0
tempDict.forEach({
let val = $0.value // we are going to use it twice
keyMultValues += $0.key * val
valTotal += val
})
let final = keyMultValues / valTotal
This uses just one pass over the data.
If your numbers are large you might need to do two passes using the "divide as you go algorithm", and if you wish to find the floating-point weighted average then you need to throw a few Double
casts in. Both of those are covered by @Rob's answer.
Is one pass over the data really better (faster, less memory, whatever) than two? That is left as an exercise! I added this answer just to show you how close your original attempt was to a correct solution.
Upvotes: 0
Reputation: 437917
Two observations:
If you're doing weighted average, you probably don't want to do integer calculation. You probably want floating point result.
You may want to avoid overflow issues stemming from multiplying all the keys by the values and then adding them up and then dividing by the sum of the values. You might want to do that value by value:
Thus:
let sumOfWeights = dictionary.reduce(0.0) { $0 + Double($1.value) }
let weightedAverage = dictionary.reduce(0.0) { $0 + Double($1.key) * Double($1.value) / sumOfWeights }
Note, this works with the original values:
let dictionary = [100:2, 101:3, 102:4]
This also works with large values, too, e.g.:
let dictionary = [
1_000_000_000: 20_000_000_000,
1_000_000_001: 30_000_000_000,
1_000_000_002: 40_000_000_000
]
Upvotes: 2
Reputation: 63331
How about this?
let keyValueProductsSum = dict.lazy.map(*).reduce(0, +)
let valueSum = dict.values.reduce(0, +)
let weightedAverage = keyValueProductsSum / valueSum
Upvotes: 0