在 Swift 中如何编写将 [String:[Int]] 转换为 [String:Int] 的函数
In Swift how to write a func that turns a [String:[Int]] to [String:Int]
我得到了应用列表及其评分:
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 作为输入,return 他们的姓名和平均评分,就像这样。
["Calendar Pro": 3,
"The Messenger": 3,
"Socialise": 2]
有谁知道如何使用 func 中的闭包来实现这样一种方法,它将(name 和 [rating])作为输入和输出(name 和 avgRating)?
这是我目前所拥有的。
func calculate( appName: String, ratings : [Int]) -> (String, Double ) {
let avg = ratings.reduce(0,+)/ratings.count
return (appName, Double(avg))
}
从根本上说,您要实现的是一组值到另一组值之间的映射。字典有一个函数,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,[=16=]+)/ratings.count
return appName: sum/avg
}
[=27=]+
不在闭包内 ({ }
),因为它需要在闭包内。
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)
}
}
这是一个考虑到评级是整数的简单解决方案:
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 { [=10=].reduce(0, +) / [=10=].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 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]
我决定为此做一个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 { [=10=].isEmpty ? 0 : average(ratings: [=10=]) }
}
}
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]
.
的结果
只是为了让 post 完成另一种使用 reduce(into:) 的方法来避免使用具有可选值类型的字典:
extension Dictionary where Key == String, Value: Collection, Value.Element: BinaryInteger {
var averageRatings: [String : Value.Element] {
return reduce(into: [:]) {
if !.value.isEmpty {
[=10=][.key] = .value.reduce(0,+) / Value.Element(.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: {[=11=].key<.key})
keySorted.map{ print([=11=],) }
Calendar Pro 3
Socialise 2
The Messenger 3
我得到了应用列表及其评分:
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 作为输入,return 他们的姓名和平均评分,就像这样。
["Calendar Pro": 3,
"The Messenger": 3,
"Socialise": 2]
有谁知道如何使用 func 中的闭包来实现这样一种方法,它将(name 和 [rating])作为输入和输出(name 和 avgRating)?
这是我目前所拥有的。
func calculate( appName: String, ratings : [Int]) -> (String, Double ) {
let avg = ratings.reduce(0,+)/ratings.count
return (appName, Double(avg))
}
从根本上说,您要实现的是一组值到另一组值之间的映射。字典有一个函数,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 将通过简单地将它们添加到累加器中来减少所有数字,累加器将从 0allRatings.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,[=16=]+)/ratings.count
return appName: sum/avg
}
[=27=]+
不在闭包内 ({ }
),因为它需要在闭包内。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)
}
}
这是一个考虑到评级是整数的简单解决方案:
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 { [=10=].reduce(0, +) / [=10=].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 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]
我决定为此做一个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 { [=10=].isEmpty ? 0 : average(ratings: [=10=]) }
}
}
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]
.
只是为了让 post 完成另一种使用 reduce(into:) 的方法来避免使用具有可选值类型的字典:
extension Dictionary where Key == String, Value: Collection, Value.Element: BinaryInteger {
var averageRatings: [String : Value.Element] {
return reduce(into: [:]) {
if !.value.isEmpty {
[=10=][.key] = .value.reduce(0,+) / Value.Element(.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: {[=11=].key<.key})
keySorted.map{ print([=11=],) }
Calendar Pro 3
Socialise 2
The Messenger 3