在Swift中,如何编写将[String:[Int]]转换为[String:Int]的函子

时间:2018-09-26 16:58:32

标签: swift func calculation

为我提供了一系列应用程序及其评分:

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

4 个答案:

答案 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
}

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

  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,$0+$1)/ratings.count
    return appName: sum/avg
}
  1. $0+$1不在闭包({ })内,而是必需的。
  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) 
    }
}

答案 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