为我提供了一系列应用程序及其评分:
let appRatings = [
"Calendar Pro": [1, 5, 5, 4, 2, 1, 5, 4],
"The Messenger": [5, 4, 2, 5, 4, 1, 1, 2],
"Socialise": [2, 1, 2, 2, 1, 2, 4, 2]
]
我想编写一个将appRating作为输入并返回其名称和平均评分的函数。
["Calendar Pro": 3,
"The Messenger": 3,
"Socialise": 2]
有人知道如何使用func内部的闭包实现将(名称和[评级])作为输入和输出(名称和avgRating)的方法吗?
这是我到目前为止所拥有的。
func calculate( appName: String, ratings : [Int]) -> (String, Double ) {
let avg = ratings.reduce(0,+)/ratings.count
return (appName, Double(avg))
}
答案 0 :(得分:2)
从根本上讲,您要实现的是将一组值转换为另一组值。字典为此提供一个Dictionary.mapValues(_:)
功能,专门用于仅映射值(将它们保持在相同的键下)。
let appRatings = [
"Calendar Pro": [1, 5, 5, 4, 2, 1, 5, 4],
"The Messenger": [5, 4, 2, 5, 4, 1, 1, 2],
"Socialise": [2, 1, 2, 2, 1, 2, 4, 2]
]
let avgAppRatings = appRatings.mapValues { allRatings in
return computeAverage(of: allRatings) // Dummy function we'll implement later
}
所以现在,要弄清楚如何对数组中所有数字求平均值。幸运的是,这很容易:
我们需要汇总所有评分
我们可以使用reduce
表达式轻松实现此目的。 StWe将通过简单地将它们添加到累加器(以0开头)来减少所有数字
allRatings.reduce(0, { accumulator, rating in accumulator + rate })
从这里,我们可以注意到闭包{ accumulator, rating in accumulator + rate }
的类型为(Int, Int) -> Int
,并将数字加在一起。好吧,这就是+
所做的!我们可以直接使用它:
allRatings.reduce(0, +)
我们需要将评分除以评分数量
Int.
,因此我们需要先将总和和计数都转换为Double
。您需要防止空数组(其计数为0,导致Double.infinity
。
将它们放在一起,我们得到:
let appRatings = [
"Calendar Pro": [1, 5, 5, 4, 2, 1, 5, 4],
"The Messenger": [5, 4, 2, 5, 4, 1, 1, 2],
"Socialise": [2, 1, 2, 2, 1, 2, 4, 2]
]
let avgAppRatings = appRatings.mapValues { allRatings in
if allRatings.isEmpty { return nil }
return Double(allRatings.reduce(0, +)) / Double(allRatings.count)
}
添加一些不错的打印逻辑:
extension Dictionary {
var toDictionaryLiteralString: String {
return """
[
\t\(self.map { k, v in "\(k): \(v)" }.joined(separator: "\n\t"))
]
"""
}
}
...和繁荣:
print(avgAppRatings.toDictionaryLiteralString)
/* prints:
[
Socialise: 2.0
The Messenger: 3.0
Calendar Pro: 3.375
]
*/
关于您的尝试为何无效的问题,您有一些疑问:
func calculate( appName: String, ratings : [Int]) -> (String: Int ) {
var avg = ratings.reduce(0,$0+$1)/ratings.count
return appName: sum/avg
}
$0+$1
不在闭包({ }
)内,而是必需的。appName: sum/avg
是无效的Swift。sum
不存在。avg
是一个var
变量,即使它从未突变。它应该是一个let
常量。Double
。固定版本可能如下:
func calculateAverage(of numbers: [Int]) -> Double {
let sum = Double(ratings.reduce(0, +))
let count = Double(numbers.count)
return sum / count
}
要创建一个处理整个字典的函数,并在上面增加我的解决方案,您可以编写类似以下的函数:
func calculateAveragesRatings(of appRatings: [String: [Int]]) -> [String: Double?] {
return appRatings.mapValues { allRatings in
if allRatings.isEmpty { return nil }
return Double(allRatings.reduce(0, +)) / Double(allRatings.count)
}
}
答案 1 :(得分:0)
这是一个简单的解决方案,考虑到评分是整数:
let appRatings = [
"Calendar Pro": [1, 5, 5, 4, 2, 1, 5, 4],
"The Messenger": [5, 4, 2, 5, 4, 1, 1, 2],
"Socialise": [2, 1, 2, 2, 1, 2, 4, 2]
]
let appWithAverageRating: [String: Int] = appRatings.mapValues { $0.reduce(0, +) / $0.count}
print("appWithAverageRating =", appWithAverageRating)
打印appWithAverageRating = ["The Messenger": 3, "Calendar Pro": 3, "Socialise": 2]
如果您要在返回平均评分之前检查某个应用是否具有足够的评分,则该评分将是可选的Int:
let minimumNumberOfRatings = 0 // You can change this
var appWithAverageRating: [String: Int?] = appRatings.mapValues { ratingsArray in
guard ratingsArray.count > minimumNumberOfRatings else {
return nil
}
return ratingsArray.reduce(0, +) / ratingsArray.count
}
如果您希望收视率提高半星(0、0.5、1,...,4.5、5),那么我们可以使用以下extension:
extension Double {
func roundToHalf() -> Double {
let n = 1/0.5
let numberToRound = self * n
return numberToRound.rounded() / n
}
}
然后,等级将为可选的Double。让我们添加一个AppWithoutRatings
并测试我们的代码:
let appRatings = [
"Calendar Pro": [1, 5, 5, 4, 2, 1, 5, 4],
"The Messenger": [5, 4, 2, 5, 4, 1, 1, 2],
"Socialise": [2, 1, 2, 2, 1, 2, 4, 2],
"AppWithoutRatings": []
]
let minimumNumberOfRatings = 0
var appWithAverageRating: [String: Double?] = appRatings.mapValues { ratingsArray in
guard ratingsArray.count > minimumNumberOfRatings else {
return nil
}
let rating: Double = Double(ratingsArray.reduce(0, +) / ratingsArray.count)
return rating.roundToHalf()
}
这将打印:
appWithAverageRating = ["Calendar Pro": Optional(3.0), "Socialise": Optional(2.0), "The Messenger": Optional(3.0), "AppWithoutRatings": nil]
答案 2 :(得分:0)
我决定为此做一个Dictionary
扩展名,因此将来很容易使用。
这是我创建的代码:
extension Dictionary where Key == String, Value == [Float] {
func averageRatings() -> [String : Float] {
// Calculate average
func average(ratings: [Float]) -> Float {
return ratings.reduce(0, +) / Float(ratings.count)
}
// Go through every item in the ratings dictionary
return self.mapValues { $0.isEmpty ? 0 : average(ratings: $0) }
}
}
let appRatings: [String : [Float]] = ["Calendar Pro": [1, 5, 5, 4, 2, 1, 5, 4],
"The Messenger": [5, 4, 2, 5, 4, 1, 1, 2],
"Socialise": [2, 1, 2, 2, 1, 2, 4, 2]]
print(appRatings.averageRatings())
将打印["Calendar Pro": 3.375, "Socialise": 2.0, "The Messenger": 3.0]
的结果。
答案 3 :(得分:0)
只是为了使帖子使用reduce(into :)完成另一种方法,以避免使用具有可选值类型的字典:
extension Dictionary where Key == String, Value: Collection, Value.Element: BinaryInteger {
var averageRatings: [String : Value.Element] {
return reduce(into: [:]) {
if !$1.value.isEmpty {
$0[$1.key] = $1.value.reduce(0,+) / Value.Element($1.value.count)
}
}
}
}
let appRatings2 = ["Calendar Pro" : [1, 5, 5, 4, 2, 1, 5, 4],
"The Messenger": [5, 4, 2, 5, 4, 1, 1, 2],
"Socialise" : [2, 1, 2, 2, 1, 2, 4, 2] ]
let keySorted = appRatings2.averageRatings.sorted(by: {$0.key<$1.key})
keySorted.map{ print($0,$1) }
Calendar Pro 3
社交2
信使3