74

I'm using a swift dictionary of type [UIImage:UIImage], and I'm trying to find a specific key for a given value. In Objective-C I could use allKeysForValue, but there appears to be no such method for a Swift dictionary. What should I be using?

mginn
  • 16,036
  • 4
  • 26
  • 54

11 Answers11

78

Swift 3: a more performant approach for the special case of bijective dictionaries

If the reverse dictionary lookup use case covers a bijective dictionary with a one to one relationship between keys and values, an alternative approach to the collection-exhaustive filter operation would be using a quicker short-circuiting approach to find some key, if it exists.

extension Dictionary where Value: Equatable {
    func someKey(forValue val: Value) -> Key? {
        return first(where: { $1 == val })?.key
    }
}

Example usage:

let dict: [Int: String] = [1: "one", 2: "two", 4: "four"]

if let key = dict.someKey(forValue: "two") { 
    print(key)
} // 2
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
dfrib
  • 70,367
  • 12
  • 127
  • 192
68

There is, as far as I know, no built-in Swift function to get all dictionary keys for a given value. Here is a possible implementation:

func allKeysForValue<K, V : Equatable>(dict: [K : V], val: V) -> [K] {
    return map(filter(dict) { $1 == val }) { $0.0 }
}

The filter reduces all key-value pairs to those with the given value. The map maps the (filtered) key-value pairs to the keys alone.

Example usage:

let dict = ["a" : 1, "b" : 2, "c" : 1, "d" : 2]
let keys = allKeysForValue(dict, 1)
println(keys) // [a, c]

Update for Swift 2: As of Xcode 7 beta 2, this can now be achieved with an extension method for dictionaries of equatable values (thanks to Airspeed Velocity to make me aware of this in a comment):

extension Dictionary where Value : Equatable {
    func allKeysForValue(val : Value) -> [Key] {
        return self.filter { $1 == val }.map { $0.0 }
    }
}

let dict = ["a" : 1, "b" : 2, "c" : 1, "d" : 2]
let keys = dict.allKeysForValue(1)
print(keys) // [a, c]

Update for Swift 3:

extension Dictionary where Value: Equatable {
    func allKeys(forValue val: Value) -> [Key] {
        return self.filter { $1 == val }.map { $0.0 }
    }
}

let dict = ["a" : 1, "b" : 2, "c" : 1, "d" : 2]
let keys = dict.allKeys(forValue: 1)
print(keys) // [a, c]
Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Is there a way to return the value as a string rather than as an array? In my setup there will always only be a single unique value for every key (it is a japanese to romanji dictionary). – sxflynn Aug 16 '15 at 03:35
  • Just thought I'd mention that I've been using this approach in my project for a while, and noticed today while profiling that it can be *very* slow... looking for an alternative. – jbm Nov 30 '16 at 19:43
  • 1
    @HashemAboonajmi: Thank you for the edit. However, I decided to revert to the previous version because I think that `allKeys(forValue:)` is correct even in Swift 3. Existing methods with similar naming are `index(forKey:)`, `update(value: forKey)`, `value(forKey:)`. – Martin R Apr 27 '17 at 06:42
45

Understand that most of these answers get the dictionary's entire set of keys before searching through it. This answer uses the first(where:) method which returns after the key is found.

Swift 5 (inline)

if let key = someDictionary.first(where: { $0.value == someValue })?.key {
    // use key
}

Swift 5 (extension)

extension Dictionary where Value: Equatable {
    func key(from value: Value) -> Key? {
        return self.first(where: { $0.value == value })?.key
    }
}

Usage

let key = someDictionary.key(from: someValue)) // optional

if let key = someDictionary.key(from: someValue) {
    // non-optional
}
trndjc
  • 11,654
  • 3
  • 38
  • 51
  • 6
    That's AWESOME! This should be the current correct answer. – Adrian Mar 05 '20 at 12:58
  • @Adrian note that this answer uses exactly the same approach as the **accepted** answer, which was posted 3 years before this one. This answer fails to mention, however, that one should use this with care, explicitly knowing that it will yield _some_ key, particularly in dictionaries where several keys stores the same value. – dfrib Oct 23 '20 at 21:03
44

You can use allKeys(for:) if you cast to NSDictionary:

let keys = (dict as NSDictionary).allKeys(for: image) as! [UIImage]
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • Thanks that works nicely, and is a nice clean solution. – mginn Dec 01 '14 at 01:32
  • Just a tiny addition, I used this code as a pattern to extract a Int key from a dictionary: `let keys = (ItemDict as NSDictionary).allKeysForObject(orderItems[indexPath.row])` Then you can access like: `let someIntValue = keys[0].integerValue;` Works like a dream, thanks! – c0d3p03t Apr 21 '15 at 21:16
  • Swift 3: `let keys = (dict as NSDictionary).allKeys(for: image) as! [UIImage]` – BitParser Jul 13 '17 at 13:50
15
let dict = ["key1":2,"key2":6,"key3":8,"key4":8]
let searchingValue = 8
let b = dict.filter {$0.value == searchingValue}
let a = b.keys.first

b provides map with searchingValue which is ["key4":8,"key3":8]

b.keys.first provides first element of the all filtered keys which is g

a is the rquired key for value 8

Learner
  • 621
  • 6
  • 9
8

Here's another approach, which I wrote about on my blog. It was tested against Swift 2.2.

extension Dictionary where Value: Equatable {
  /// Returns all keys mapped to the specified value.
  /// ```
  /// let dict = ["A": 1, "B": 2, "C": 3]
  /// let keys = dict.keysForValue(2)
  /// assert(keys == ["B"])
  /// assert(dict["B"] == 2)
  /// ```
  func keysForValue(value: Value) -> [Key] {
    return flatMap { (key: Key, val: Value) -> Key? in
      value == val ? key : nil
    }
  } 
}

It's the most efficient implementation posted to this thread that yields all keys mapped to a specified value, because it uses flatMap, instead of filter and then map. I wrote about flatMap in my Higher-order functions in Swift article, if you're interested.

Also, because my method is generic (by virtue of being in the Dictionary<Key,Value> generic class) you don't need to cast its result to the key's type, which is necessary when using allKeysForObject(_:) from NSDictionary.

Josh Smith
  • 884
  • 6
  • 9
5

The Swift 2 version:

func allKeysForValue<K, V : Equatable>(dict: [K : V], val: V) -> [K] {
    return dict.filter{ $0.1 == val }.map{ $0.0 }
}
Teo Sartori
  • 1,082
  • 14
  • 24
  • Why it is `$0.1` and `$0.0` instead of `$0` and `$1` please? – JW.ZG Jul 12 '16 at 00:54
  • Because you are mapping over a dictionary, $0 is a dictionary element. $0.0 is the key and $0.1 is the value. – Teo Sartori Jul 12 '16 at 05:45
  • I'm sorry, I can't get your point. You said *$0 is a dictionary element*, but `key` and `value` are dictionary elements, right? Because `dictionary = [key:valule]`. – JW.ZG Jul 12 '16 at 16:19
  • Do you mean `$0` is the first element in dictionary? Like `[firstKey:firstValue]`? – JW.ZG Jul 12 '16 at 16:20
  • 1
    In Swift Dictionaries are implemented as generic collections. The contents of a collection are accessed by iterating over its elements. In the case of a Dictionary these elements are defined as a tuples of key, value pairs. You can access the individual parts of a tuple using an index, so $0.0 corresponds to the first (a key) and $0.1 to the second (the value related to the key). – Teo Sartori Jul 13 '16 at 06:12
5

If you need to find some key for a value (i.e. not all of them if there is more than one, but just any arbitrary one):

extension Dictionary where Value: Equatable {

    func someKeyFor(value: Value) -> Key? {

        guard let index = indexOf({ $0.1 == value }) else {
            return nil
        }

        return self[index].0

    }

}

Above returns nil if there is no such key for a value.

0x416e746f6e
  • 9,872
  • 5
  • 40
  • 68
1

This will work in case you need any key for a specific value. In my case was the easiest approach. Hope that it helps:

Swift 4.1+

extension Dictionary where Key == String, Value: Equatable {
    func key(for value: Value) -> Key? {
        return compactMap { value == $1 ? $0 : nil }.first
    }
}
davebcn87
  • 819
  • 7
  • 4
0

If you don't know that the values are unique, this is an option:

public extension Dictionary where Value: Equatable {
  /// The only key that maps to `value`.
  /// - Throws: `OnlyMatchError`
  func onlyKey(for value: Value) throws -> Key {
    try onlyMatch { $0.value == value } .key
  }
}
public extension Sequence {
  /// The only match for a predicate.
  /// - Throws: `OnlyMatchError`
  func onlyMatch(for getIsMatch: (Element) throws -> Bool) throws -> Element {
    guard let onlyMatch: Element = (
      try reduce(into: nil) { onlyMatch, element in
        switch ( onlyMatch, try getIsMatch(element) ) {
        case (_, false):
          break
        case (nil, true):
          onlyMatch = element
        case (.some, true):
          throw onlyMatchError.moreThanOneMatch
        }
      }
    ) else { throw onlyMatchError.noMatches }

    return onlyMatch
  }
}

/// An error thrown from a call to `onlyMatch`.
public enum OnlyMatchError: Error {
  case noMatches
  case moreThanOneMatch
}
XCTAssertEqual(
  try ["skunky": "monkey", "": ""].onlyKey(for: ""),
  ""
)
0

You can just filter through the dictionary and find the value that matches the value you're looking for and then get all the keys that are paired with that value in the dictionary.

FYI: someValue in this answer represents the hypothetical value you're looking for

dictionary.filter{$0.value == someValue}.keys
Newbie
  • 164
  • 7