Find maximum integer value in array of dictionaries

Hi guys,


I have an array of dictionaries which with following sample data:


var dictArray = [["totalRating":100,"uid":"Id-1"],["totalRating":200,"uid":"Id-2"],["totalRating":20,"uid":"Id-4"]]


How do I get the single dictionary object with the max "totalRating"?

Note: I am using Swift 4.1


Thanks in advance!

Replies

Here is a solution


var dictArray = [["totalRating":100,"uid":"Id-1"],["totalRating":200,"uid":"Id-2"],["totalRating":20,"uid":"Id-4"]]

var max = 0
var highest : [String:Any] = [:]
for user in dictArray { // ["totalRating":100,"uid":"Id-1"]
    if let val = user["totalRating"] as? Int, val > max {
        max = val
        highest = user
    }
}

print(highest)


yields

["totalRating": 200, "uid": "Id-2"]


It can certainly be written with filter or map or reduce, but that makes it pretty hard to read.

hi,


Claude31's response is exactly what you want. but i'll go a little farther ...


you're assuming that there is a unique, largest "totalRating." if more than one has the highest rating (and all the dictionaries have an integer value for "totalRating"), what could you do? the following functional approach would do the trick:


var dictArray = [["totalRating":100,"uid":"Id-1"],["totalRating":200,"uid":"Id-2"],["totalRating":20,"uid":"Id-4"],["totalRating":200,"uid":"Id-49"]]

let largestValue = dictArray.map( { $0["totalRating"] as! Int } ).max()!
let largestElements = dictArray.filter() { ($0["totalRating"] as! Int) == largestValue }
print(largestElements)

this yields an array of all (two, in this case) dictionary items with the same largest, totalRating:


[["totalRating": 200, "uid": "Id-2"], ["uid": "Id-49", "totalRating": 200]]


hope that's also of interest in addition to Claude31's solution.


DMG

It can certainly be written with filter or map or reduce, but that makes it pretty hard to read.

It might be hard to read if you build it on those primitives, but in this case there’s a

max(_:)
method that does exactly this job. It also saves you from one gotcha, namely that it returns an optional value, making it clear that there may be no maximum. The code you posted assumes that 0 is a valid sentinel for that, but there’s no guarantee that this is the case.

So, if you want to assume that every dictionary has a valid

totalRating
property, you could write this:
let dictArray = [
    ["totalRating":100,"uid":"Id-1"],
    ["totalRating":200,"uid":"Id-2"],
    ["totalRating":20,"uid":"Id-4"]
]
let d = dictArray.max { l, r in
    return (l["totalRating"] as! Int) > (r["totalRating"] as! Int)
}

where

d
is an optional dictionary. If you don’t want to assume that, then you could write this:
let d = dictArray.max { l, r in
    let lRating = l["totalRating"] as? Int
    let rRating = r["totalRating"] as? Int
    switch (lRating, rRating) {
    case (nil, nil): return false
    case (nil, _): return true
    case (_, nil): return false
    case (let l?, let r?): return l < r
    }
}

although I prefer this:

let d = dictArray.compactMap { element in
    (element["totalRating"] as? Int).flatMap { (value: $0, element: element) }
}.max { l, r in
    l.value < r.value
}.flatMap { $0.element }

Personally, I’d simplify the whole problem by getting rid of the

[String: Any]
dictionary. If it’s a precondition of this code that every item has a
totalRating
value, I’d check that when I import the data into my app and then represent each item as a
Rating
type:
struct Rating {
    var totalRating: Int
    var uid: String     // use `String?` if `uid` is not mandatory
}

That allows for a radical simplification of the code:

let dictArray = [
    Rating(totalRating: 100, uid: "Id-1"),
    Rating(totalRating: 200, uid: "Id-2"),
    Rating(totalRating: 20, uid: "Id-4"),
]
let d = dictArray.max { l, r in
    l.totalRating < r.totalRating
}

And if you want all items with that total rating, that’s just a

flatMap(_:)
away:
let dictArray = [
    Rating(totalRating: 100, uid: "Id-1"),
    Rating(totalRating: 200, uid: "Id-2"),
    Rating(totalRating: 20, uid: "Id-4"),
    Rating(totalRating: 200, uid: "Id-5"),
]
let all = dictArray.max { l, r in
    l.totalRating < r.totalRating
}.flatMap { d in
    dictArray.filter { $0.totalRating == d.totalRating }
} ?? []

One final advantage of using a real type here is that, if the data is coming from a web service, you could make the

Rating
type
Codable
and thus easily build it from the JSON returned by that web service.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

great food for thought, eskimo, especially with all the variations! well done.


thanks,

DMG

Thanks a lot 🙂

Thanks a lot 🙂

Don't forget to close the thread. And good continuation.