В Swift, как написать функцию, которая превращает [String: [Int]] в [String: Int] - PullRequest
0 голосов
/ 26 сентября 2018

Мне дали список приложений вместе с их рейтингами:

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]

Кто-нибудь знает, как реализовать такой метод, который он использует (имя и [рейтинг]) в качестве входных и выходных данных (имя и avgRating), используя замыкание внутри функции?

Это то, что ядо сих пор.

func calculate( appName: String, ratings : [Int]) -> (String, Double ) {
    let avg = ratings.reduce(0,+)/ratings.count

    return (appName, Double(avg))
}

Ответы [ 4 ]

0 голосов
/ 27 сентября 2018

Просто чтобы завершить публикацию другим способом, использующим приёмник (в :), чтобы избежать использования словаря с необязательным типом значения:

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

КалендарьPro 3

Общайтесь 2

Посланник 3

0 голосов
/ 26 сентября 2018

Это простое решение, которое учитывает, что рейтинг представляет собой целое число:

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 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]
0 голосов
/ 26 сентября 2018

Я решил создать для этого расширение 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].

0 голосов
/ 26 сентября 2018

По сути, вы пытаетесь достичь соответствия между одним набором значений и другим.Для этого в словаре есть функция 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.Мы уменьшим все числа, просто добавив их в аккумулятор, который будет начинаться с 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"))
        ]
        """
    }
}

... и boom:

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