56

Is there are analog of - (NSArray *)keysSortedByValueUsingSelector:(SEL)comparator in swift?

How to do this without casting to NSDictionary?

I tried this, but it seems to be not a good solution.

var values = Array(dict.values)
values.sort({
    $0 > $1
    })

for number in values {
    for (key, value) in dict {
        if value == number {
            println(key + " : \(value)");
            dict.removeValueForKey(key);
            break
        }
    }
}

Example:

var dict = ["cola" : 10, "fanta" : 12, "sprite" : 8]
dict.sortedKeysByValues(>) // fanta (12), cola(10), sprite(8)
Gabriele Petronella
  • 106,943
  • 21
  • 217
  • 235
NikeAlive
  • 573
  • 1
  • 5
  • 8

19 Answers19

96

Just one line code to sort dictionary by Values in Swift 4, 4.2 and Swift 5:

let sortedByValueDictionary = myDictionary.sorted { $0.1 < $1.1 }
pkamb
  • 33,281
  • 23
  • 160
  • 191
S.S.D
  • 1,579
  • 2
  • 12
  • 23
  • 2
    It works for me in Swift 4.1 using Dictionary – eharo2 Jul 16 '18 at 21:13
  • 2
    N.B. returns an array of tuples `(key, value)`, so you'll need to read the contents like this if you want the object: `array[x].1` – Ash Oct 06 '20 at 06:54
  • 1
    I am fairly new to Swift, and think that this answer could be made stronger by briefly explaining the syntax `{ $0.1 < $1.1 }` – John Harrington May 26 '22 at 22:42
  • Thanks, @Ash for the note! In my case, I needed to get the key for each tuple, by doing: `array[x].key` – jakob.j May 27 '22 at 07:35
  • On another note, in my case I needed to sort by values which are numbers in strings, so I ended up with: `let sortedByValueDictionary = myDictionary.sorted {$0.value.localizedStandardCompare($1.value) == .orderedAscending}` (inspired by https://stackoverflow.com/a/43870210) – jakob.j May 27 '22 at 08:11
  • 3
    @JohnHarrington: so as a try for an explanation: the `$0` and `$1` are the "two pseudo entries" for defining the compare function (which is then applied by the `sorted` function on all entries of the dictionary). Since we want to compare by value, we have to use the second element (0 is the key, 1 is the value), hence the `.1`. (So you can also write `{ $0.value < $1.value }`.) – jakob.j May 27 '22 at 08:22
28

Try:

let dict = ["a":1, "c":3, "b":2]

extension Dictionary {
    func sortedKeys(isOrderedBefore:(Key,Key) -> Bool) -> [Key] {
        return Array(self.keys).sort(isOrderedBefore)
    }

    // Slower because of a lot of lookups, but probably takes less memory (this is equivalent to Pascals answer in an generic extension)
    func sortedKeysByValue(isOrderedBefore:(Value, Value) -> Bool) -> [Key] {
        return sortedKeys {
            isOrderedBefore(self[$0]!, self[$1]!)
        }
    }

    // Faster because of no lookups, may take more memory because of duplicating contents
    func keysSortedByValue(isOrderedBefore:(Value, Value) -> Bool) -> [Key] {
        return Array(self)
            .sort() {
                let (_, lv) = $0
                let (_, rv) = $1
                return isOrderedBefore(lv, rv)
            }
            .map {
                let (k, _) = $0
                return k
            }
    }
}

dict.keysSortedByValue(<)
dict.keysSortedByValue(>)

Updated:

Updated to the new array syntax and sort semantics from beta 3. Note that I'm using sort and not sorted to minimize array copying. The code could be made more compact, by looking at the earlier version and replacing sort with sorted and fixing the KeyType[] to be [KeyType]

Updated to Swift 2.2:

Changed types from KeyType to Key and ValueType to Value. Used new sort builtin to Array instead of sort(Array) Note performance of all of these could be slightly improved by using sortInPlace instead of sort

David Berry
  • 40,941
  • 12
  • 84
  • 95
  • Very interesting, but not what I need. It's only sorted keys. But I need keys sorted by values. Smth like dict.sortedKeysByValues(>) --> b, c, a; dict.sortedKeysByValues(<) --> a, c, b; – NikeAlive Jun 06 '14 at 21:45
  • Sorry, misread what you were doing as just wanting the keys sorted. – David Berry Jun 06 '14 at 21:50
  • 1
    @NikeAlive edited to do what you want, thanks for the problem :) – David Berry Jun 06 '14 at 22:02
  • writing with Swift suppose to be simpler than objective-c? without casting to NSDictionary this is not easiest way to sort... or i am missing something? – modusCell Jun 09 '14 at 19:18
  • @mohacs you're missing something. Sorting the keys of a dictionary by value is about the same level of complexity in Objective-C. Note that my answer presents two completely different methods of doing the same thing, the first of which is pretty much identical to what it would be in Objective-C. – David Berry Jun 09 '14 at 20:49
  • probably i am, because with Obj-c i am sorting the keys of a dictionary by value with this NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:@"KeyName" ascending:NO]; then one more line. can you explain what it the difference? thanks. – modusCell Jun 09 '14 at 21:00
  • @mohacs I think you're confusing sorting an array of dictionaries by the value associated with a particular key (what you seem to be referring to) and sorting the keys of a dictionary by value in the dictionary which is what the OP is asking about. – David Berry Jun 09 '14 at 21:23
  • @David definitely confusion, you are right. now i understand thank you. – modusCell Jun 09 '14 at 21:28
  • This doesn't compile with Xcode Beta 3. `KeyType[]` is now `[KeyType]` but that's only a warning, the compile error is on the `return sort(Array(...`. Compiler says `'NSArray' is not a subtype of 'inout C'` whatever that means!?! – progrmr Jul 08 '14 at 02:11
  • I've got it compiling in Xcode Beta3 now but I can't test it due to unrelated issues, the [updated code is here](https://github.com/progrmr/State_Hunt_swift/blob/master/State_Hunt/NSDictionary%2BSort.swift#L12) – progrmr Jul 08 '14 at 03:17
  • They've changed the semantics of `sort` to take an inout array that is sorted in place. The old functionality is now available as `sorted` I'll update the answers tomorrow when I'm at a computer to validate everything. – David Berry Jul 08 '14 at 05:37
  • 2
    The above code gives me in Xcode 6 Beta5 the errors: "Use of undeclared type 'KeyType'" and if I change all KeyType/ValueType by Int the compiler says: "'Key' is not a subtype of 'Int'" and "'Key' is not identical to 'Int'" in the first "func SortedKeys...". Any idea or fix for Xcode6 Beta5? – iOS-Coder Aug 14 '14 at 19:46
  • As of Swift 1.2 and Xcode 6.3.1 all that needs to be changed in order to compile in this extension is to change throughout `KeyType` to `Key` and `ValueType` to `Value` – Max Phillips Sep 05 '15 at 18:53
  • Updated the answer to Swift 2.2 syntax and functions. – David Berry May 31 '16 at 22:55
24

You could use something like this perhaps:

var dict = ["cola" : 10, "fanta" : 12, "sprite" : 8]

var myArr = Array(dict.keys)
var sortedKeys = sort(myArr) {
    var obj1 = dict[$0] // get ob associated w/ key 1
    var obj2 = dict[$1] // get ob associated w/ key 2
    return obj1 > obj2
}

myArr // ["fanta", "cola", "sprite"]
Logan
  • 52,262
  • 20
  • 99
  • 128
  • 1
    The above gives in Xcode6 Beta5 the following error: "Passing value of type 'Array' to an inout parameter requires explicit '&'". However if I fix this to "var sortedKeys = sort(&myArr)" I get the warning: "Variable 'sortedKeys' inferred to have type '()', which may be unexpected". Fixing this to "var sortedKeys: () = sort(&myArr)" gives the specified result for myArr. Any idea on how do we sort on keys (if possible)? See also my comment to @David's code in the accepted answer. – iOS-Coder Aug 14 '14 at 20:03
  • 6
    `sort(myArr)`, sort does not exist in Swift 4 anymore. – Van Du Tran Jan 02 '18 at 20:08
  • 2
    Use myArr.sorted() for swift 4 – Kanika Sharma Jan 24 '19 at 09:20
21

This should give you the sorted keys based on value, and is a little more cleaner:

var sortedKeys = Array(dict.keys).sorted(by: { dict[$0]! < dict[$1]! })
Pranav Kasetti
  • 8,770
  • 2
  • 50
  • 71
pulse4life
  • 1,006
  • 10
  • 13
12

OneLiner :

let dict = ["b": 2, "a": 1, "c": 3]
(Array(dict).sorted { $0.1 < $1.1 }).forEach { (k,v) in print("\(k):\(v)") }
//Output: a:1, b:2, c:3

Swap out the .forEach with .map -> Functional programming

Syntactical sugar :

extension Dictionary where Value: Comparable {
    var sortedByValue: [(Key, Value)] { return Array(self).sorted { $0.1 < $1.1} }
}
extension Dictionary where Key: Comparable {
    var sortedByKey: [(Key, Value)] { return Array(self).sorted { $0.0 < $1.0 } }
}
["b": 2, "a": 1, "c": 3].sortedByKey // a:1, b:2, c:3
["b": 2, "a": 1, "c": 3].sortedByValue // a:1, b:2, c:3
Sentry.co
  • 5,355
  • 43
  • 38
11

I think this is the easiest way to sort Swift dictionary by value.

let dict = ["apple":1, "cake":3, "banana":2]

let byValue = {
    (elem1:(key: String, val: Int), elem2:(key: String, val: Int))->Bool in
    if elem1.val < elem2.val {
        return true
    } else {
        return false
    }
}
let sortedDict = dict.sort(byValue)
jeongmin.cha
  • 768
  • 8
  • 22
8

Sorting your keys by the dictionary's value is actually simpler than it appears at first:

let yourDict = ["One": "X", "Two": "B", "Three": "Z", "Four": "A"]
let sortedKeys = yourDict.keys.sort({ (firstKey, secondKey) -> Bool in
    return yourDict[firstKey] < yourDict[secondKey]
})

And that's it! There's really nothing more to it. I have yet to find a quicker method, other than the same approach in form of a simple one-liner:

let yourDict = ["One": "X", "Two": "B", "Three": "Z", "Four": "A"]
let sortedKeys = yourDict.keys.sort { yourDict[$0] < yourDict[$1] }
Martin
  • 215
  • 4
  • 9
8

Lots of answers, here's a one-liner. I like it because it makes full use of native Swift iterative functions and doesn't use variables. This should help the optimiser do its magic.

return dictionary.keys.sort({ $0 < $1 }).flatMap({ dictionary[$0] })

Note the use of flatMap, because subscripting a dictionary returns an optional value. In practice this should never return nil since we get the key from the dictionary itself. flatMap is there only to ensure that the result is not an array of optionals. If your array's associated value should BE an optional you can use map instead.

Ash
  • 9,064
  • 3
  • 48
  • 59
6

Sorting a dictionary by key or value

Using Swift 5.2 internal handling of "sorted":

var unsortedDict = ["cola" : 10, "fanta" : 12, "sprite" : 8]

// sorting by value
let sortedDictByValue = unsortedDict.sorted{ $0.value > $1.value } // from lowest to highest using ">"
print("sorted dict: \(sortedDictByValue)")
// result: "sorted dict: [(key: "fanta", value: 12), (key: "cola", value: 10), (key: "sprite", value: 8)]\n"

// highest value
print(sortedDictByValue.first!.key)  // result: fanta
print(sortedDictByValue.first!.value)  // result: 12
// lowest value
print(sortedDictByValue.last!.key)  // result: sprite
print(sortedDictByValue.last!.value)  // result: 8
// by index
print(sortedDictByValue[1].key)  // result: cola
print(sortedDictByValue[1].value)  // result: 10

// sorting by key
let sortedDictByKey = unsortedDict.sorted{ $0.key < $1.key } // in alphabetical order use "<"
// alternative:
// let sortedDictByKey = unsortedDict.sorted{ $0 < $1 }  // without ".key"
print("sorted dict: \(sortedDictByKey)")
// result: "sorted dict: [(key: "cola", value: 10), (key: "fanta", value: 12), (key: "sprite", value: 8)]\n"

// highest value
print(sortedDictByKey.first!.key)  // result: cola
print(sortedDictByKey.first!.value)  // result: 10
// lowest value
print(sortedDictByKey.last!.key)  // result: sprite
print(sortedDictByKey.last!.value)  // result: 8
// by index
print(sortedDictByKey[1].key)  // result: fanta
print(sortedDictByKey[1].value)  // result: 12
5

The following might be useful if you want the output to be an array of key value pairs in the form of a tuple, sorted by value.

var dict = ["cola" : 10, "fanta" : 12, "sprite" : 8]
let sortedArrByValue = dict.sorted{$0.1 > $1.1}
print(sortedArrByValue) // output [(key: "fanta", value: 12), (key: "cola", value: 10), (key: "sprite", value: 8)]
cypher
  • 101
  • 1
  • 3
5

Since Swift 3.0 Dictionary has sorted(by:) function which returns an array of tuples ([(Key, Value)]).

let sorted = values.sorted(by: { (keyVal1, keyVal2) -> Bool in
    keyVal1.value > keyVal2.value
})
kelin
  • 11,323
  • 6
  • 67
  • 104
4

Just cast it to NSDictionary and then call the method. Anywhere you use @selector in ObjC you can just use a String in Swift. So it would look like this:

var dict = ["cola" : 10, "fanta" : 12, "sprite" : 8]
let sortedKeys = (dict as NSDictionary).keysSortedByValueUsingSelector("compare:")

or

let sortedKeys2 = (dict as NSDictionary).keysSortedByValueUsingComparator 
                  { 
                       ($0 as NSNumber).compare($1 as NSNumber) 
                  }
ahruss
  • 2,070
  • 16
  • 21
4

As of Swift 3, to sort your keys based on values, the below looks promising:

var keys = Array(dict.keys)        
keys.sortInPlace { (o1, o2) -> Bool in
    return dict[o1]! as! Int > dict[o2]! as! Int
}
judepereira
  • 1,239
  • 2
  • 17
  • 24
3

var dict = ["cola" : 10, "fanta" : 12, "sprite" : 8]

let arr = dic.sort{ (d1,d2)-> Bool in
if d1.value > d2.value {
    retrn true
}

}.map { (key,value) -> Int in
return value
}

Take look a clean implementation way. print("arr is :(arr)")

knight2016
  • 539
  • 6
  • 12
2

The following way in Swift 3 sorted my dictionary by value in the ascending order:

for (k,v) in (Array(dict).sorted {$0.1 < $1.1}) {
    print("\(k):\(v)")
}
Darius Miliauskas
  • 3,391
  • 4
  • 35
  • 53
2

SWIFT 3:

Using a few resources I put this beautifully short code together.

dictionary.keys.sorted{dictionary[$0]! < dictionary[$1]!}

This returns an array of the dictionary keys sorted by their values. It works perfectly & doesn't throw errors when the dictionary is empty. Try this code in a playground:

//: Playground - noun: a place where people can play

import UIKit

let dictionary = ["four": 4, "one": 1, "seven": 7, "two": 2, "three": 3]

let sortedDictionary = dictionary.keys.sorted{dictionary[$0]! < dictionary[$1]!}

print(sortedDictionary)
// ["one", "two", "three", "four", "seven"]


let emptyDictionary = [String: Int]()

let emptyDictionarySorted = emptyDictionary.keys.sorted{emptyDictionary[$0]! < emptyDictionary[$1]!}

print(emptyDictionarySorted)
// []

If you'd like some help on why the heck the code uses $0, $1 and doesn't even have parentheses after the "sorted" method, check out this post - https://stackoverflow.com/a/34785745/7107094

Trev14
  • 3,626
  • 2
  • 31
  • 40
1

This is how I did it - sorting in this case by a key called position. Try this in a playground:

var result: [[String: AnyObject]] = []
result.append(["name" : "Ted", "position": 1])
result.append(["name" : "Bill", "position": 0])
result


result = sorted(result, positionSort)

func positionSort(dict1: [String: AnyObject], dict2: [String: AnyObject]) -> Bool {
    let position1 = dict1["position"] as? Int ?? 0
    let position2 = dict2["position"] as? Int ?? 0
    return position1 < position2
}
Codezy
  • 5,540
  • 7
  • 39
  • 48
1

Sorting the dictionary with a dictionary as the value (Nested dictionary)

   var students: [String: [String: Any?]] = ["16CSB40" : ["Name": "Sunitha", "StudentId": "16CSB40", "Total": 90], "16CSB41" : ["Name": "Vijay", "StudentId": "16CSB40", "Total": 80], "16CSB42" : ["Name": "Tony", "StudentId": "16CSB42", "Total": 95]] // Sort this dictionary with total value

    let sorted = students.sorted { (($0.1["Total"] as? Int) ?? 0) < (($1.1["Total"] as? Int) ?? 0) }

    print(sorted) //Sorted result
0

Use this, and then just loop through the dictionary again using the output keys.

extension Dictionary where Value: Comparable {
  func sortedKeysByValue() -> [Key] {
    keys.sorted { return self[$0]! < self[$1]! }
  }
}

...or this if you hate force unwrapping :)

extension Dictionary where Value: Comparable {
  func sortedKeysByValue() -> [Key] {
    keys.sorted { (key1, key2) -> Bool in
      guard let val1 = self[key1] else { return true }
      guard let val2 = self[key2] else { return true }
      return val1 < val2
    }
  }
}
Hlung
  • 13,850
  • 6
  • 71
  • 90