7 Replies
      Latest reply on May 17, 2019 11:25 PM by Claude31
      testUser3434 Level 1 Level 1 (0 points)

        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!

        • Re: Find maximum integer value in array of dictionaries
          Claude31 Level 8 Level 8 (6,365 points)

          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.

            • Re: Find maximum integer value in array of dictionaries
              DelawareMathGuy Level 2 Level 2 (90 points)

              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

              • Re: Find maximum integer value in array of dictionaries
                eskimo Apple Staff Apple Staff (11,625 points)

                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"

                • Re: Find maximum integer value in array of dictionaries
                  testUser3434 Level 1 Level 1 (0 points)

                  Thanks a lot