在 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
}

所以现在,问题是弄清楚如何对数组中的所有数字进行平均。幸运的是,这很容易:

  1. 我们需要对所有评分求和

    • 我们可以使用 reduce 表达式轻松实现这一点。 StWe 将通过简单地将它们添加到累加器中来减少所有数字,累加器将从 0

      开始
      allRatings.reduce(0, { accumulator, rating in accumulator + rate })
      

      从这里,我们可以注意到闭包 { accumulator, rating in accumulator + rate } 的类型为 (Int, Int) -> Int,并且只是将数字相加。好吧,这正是 + 所做的!我们可以直接使用它:

      allRatings.reduce(0, +)
      
  2. 我们需要将评分除以评分数

    • 这里有一个陷阱。为了使平均值有任何用处,它不能被截断为纯粹的 Int. 所以我们需要先将总和和计数都转换为 Double
  3. 您需要防止空数组,其计数将为 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
}
  1. [=27=]+ 不在闭包内 ({ }),因为它需要在闭包内。
  2. appName: sum/avg 无效 Swift.
  3. 变量sum不存在。
  4. avg 是一个 var 变量,即使它从未发生过变异。它应该是一个 let 常量。
  5. 您正在进行整数除法,不支持小数。首先,您需要将求和和计数转换为浮点类型,例如 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