I know that this topic has been already discussed but I can't solve looking other answers, so sorry in advance for my ripetion!
I need to sort this Dictionary by keys
codeValueDict = ["us": "$", "it": "€", "fr": "€"]
so I need a dictionary like this
sortedDict = ["fr": "€", "it": "€", "us": "$"]
but I can't do that.
I tried this
let sortedKeysAndValues = sorted(dictionary) { $0.0 < $1.0 }
but after I need to create two arrays from this dictionary (keys and values) and, using that solution
codesArray = sortedKeysAndValues.keys.array
give me the error '[(String, String)]' does not have a member named 'keys' because that solution doesn't return exactly a dictionary.
So i tried another solution:
let prova = codiceNomeDict as NSDictionary
for (k,v) in (Array(codiceNomeDict).sorted {$0.1 < $1.1}) {
let value = "[\"\(k)\": \"\(v)\"]"
println(value)
}
Which works good but then I don't know how create a new dictionary of values.
What's the best solution? How to make it works?
The output of sorted function above is an Array. So you cannot get keys & values like a Dictionary. But you can use map function to retrieve those sorted keys & values
Return an Array containing the sorted elements of source{according}.
The sorting algorithm is not stable (can change the relative order of
elements for which isOrderedBefore does not establish an order).
let codeValueDict = ["us": "$", "it": "€", "fr": "€"]
let sortedArray = sorted(codeValueDict, {$0.0 < $1.0})
print(sortedArray)
let keys = sortedArray.map {return $0.0 }
print(keys)
let values = sortedArray.map {return $0.1 }
print(values)
Dictionaries are not ordered. If you want to enumerate over them in order, you can do that using #HoaParis's solution (which is my preference), or also
for (k,v) in sorted(codiceNomeDict, {$0.1 < $1.1}) { ... }
which is just a little better way than what you were doing before because it doesn't generate a temporary array.
But if you really want "a collection that maps one value to another and is ordered by its key" then you need to create some other data structure for that. So let's do that. It's a good learning experience.
This version just implements SequenceType and provides a get/set subscript, which is most of what you'd generally want. Making it a full CollectionType is a bit of a pain I think, since startIndex and endIndex hae to be O(1). Possible; just more than I want to do this morning.
Note the major addition of Key: Comparable. That's why Dictionary can't be ordered. There's no promise that you can sort their keys. By adding that requirement, we can.
struct SortedDictionary<Key: Hashable, Value where Key: Comparable>: SequenceType {
private var dict: Dictionary<Key, Value>
init(_ dict: Dictionary<Key, Value>) {
self.dict = dict
}
func generate() -> GeneratorOf<(Key, Value)> {
let values = Array(zip(self.dict.keys, self.dict.values))
.sorted {$0.0 < $1.0 }
return GeneratorOf(values.generate())
}
subscript(key: Key) -> Value? {
get { return self.dict[key] }
set(value) { self.dict[key] = value }
}
}
var codeValueDict = ["us": "$", "it": "€", "fr": "€"]
var sortedDict = SortedDictionary(codeValueDict)
for (k, v) in sortedDict {
println("\(k) => \(v)")
}
sortedDict["us"]
sortedDict["ab"] = "!"
sortedDict
Why would you bother with SortedDictionary when you already have sorted()? Well, usually I wouldn't. But it does offer opportunities for abstraction. You could control sort order at object creation rather than at object enumeration. You could potentially cache the sort order (though I suspect in most cases that will hurt rather than help).
But I recommend just using sorted in general.
Swift doesn't include a sorted dictionary type, and dictionaries cannot be sorted. You could add an extension that offers sorting to [(Key, Value)] by doing this:
extension Dictionary {
func sort(isOrderedBefore: (Key, Key) -> Bool) -> [(Key, Value)] {
var result: [(Key, Value)] = []
let sortedKeys = keys.array.sorted(isOrderedBefore)
for key in sortedKeys {
result.append(key, self[key]!)
}
return result
}
}
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.
You can't sort dictionary in such easy way. I think dictionary is using some sort of tree data structure. But after sorting you get an array of tuples. So you can get keys in such way:
let codeValueDict = ["us": "$", "it": "€", "fr": "€"]
let sortedKeysAndValues = sorted(codeValueDict) { $0.0 < $1.0 }
let keys = sortedKeysAndValues.map {$0.0 }
let values = sortedKeysAndValues.map {$0.1 }
Sort keys case-insensitive in Swift 2
Here is a function which returns a case-insensitive sorted array of keys (or any String values).
Please keep in mind that Swift’s dictionary data structure can not be stored sorted by keys in memory. So yes, you can sort it by keys but if you print it for example then the key order is again random.
/// returns an array of values sorted by values case-insensitive
func sortCaseInsensitive(values:[String]) -> [String]{
let sortedValues = values.sort({ (value1, value2) -> Bool in
if (value1.lowercaseString < value2.lowercaseString) {
return true
} else {
return false
}
})
return sortedValues
}
Call with
let dict = ["world": "Hello!", "foo": "bar", "zYeah": "a", "akey": "xval"]
let sortedKeys = sortCaseInsensitive(Array(dict.keys))
Related
I have two dictionaries in Swift with few similar values which are in dynamic mode:
dict1 = ["a1":"value 1", "b1":"value2", "c1":"value 3"]
dict2 = ["b1": "value2", "d1": "value4"]
If I want to compare these two dictionaries and want to extract only the matching keys even nested, how do I about to do that?
If you want the common keys with the value in one of them :
let intersectionDict = dict1.filter { dict2.keys.contains($0.key) }
//Or
let intersectionDict2 = dict2.filter { dict1.keys.contains($0.key) }
If you want the values to match too:
let intersectionDict3 = dict1.filter { dict2[$0.key] == $0.value }
And the result is:
print(intersectionDict3) //["b1": "value2"]
As others have shown, you can do this using a filter statement. You can make it even quicker by always filtering the smaller of the two dicts, improving the time complexity from O(dict1.size) to O(min(dict1.size, dict2.size).
extension Dictionary {
func intersectingByKeys(with other: Dictionary) -> Dictionary {
let (smallerDict, largerDict) = (self.count < other.count) ? (self, other) : (other, self)
return smallerDict.filter { key, _ in largerDict.keys.contains(key) }
}
}
let dict1 = ["a1":"value 1", "b1":"value2", "c1":"value 3"]
let dict2 = ["b1": "value2", "d1": "value4"]
print(dict1.intersectingByKeys(with: dict2))
You can create a Set from the keys of one of the dictionaries and call intersection on the Set with the keys of the other dictionary.
let matchingKeys = Set(dict1.keys).intersection(dict2.keys) // {"b1"}
I'm trying to figure out the best way in Swift to add values to an Array that is a Value in a Dictionary. I want to build a dictionary of contacts sorted by the first letter of their first name. For example [A : [Aaron, Adam, etc...], B : [Brian, Brittany, ect...], ...]
I found this function:
updateValue(_:forKey:)
And tried using it in a loop:
for contact in self.contacts.sorted() {
self.contactDictionary.updateValue([contact], forKey: String(describing: contact.characters.first))
}
But when I tried to use that it replaced the existing array with a new one. I know I can manually check to see if the key in the dictionary exists, if it does, retrieve the array and then append a new value, otherwise add the new key/value pair but I'm not sure if Swift provides an easier/better way to do this.
Any insight would be much appreciated!
You can use reduce(into:) method (Swift4) and as follow:
let contacts = ["Aaron", "Adam", "Brian", "Brittany", ""]
let dictionary = contacts.reduce(into: [String:[String]]()) { result, element in
// make sure there is at least one letter in your string else return
guard let first = element.first else { return }
// create a string with that initial
let initial = String(first)
// initialize an array with one element or add another element to the existing value
result[initial] = (result[initial] ?? []) + [element]
}
print(dictionary) // ["B": ["Brian", "Brittany"], "A": ["Aaron", "Adam"]]
If you are using Swift3 or earlier you would need to create a mutable result dictionary inside the closure:
let contacts = ["Aaron", "Adam", "Brian", "Brittany", ""]
let dictionary = contacts.reduce([String:[String]]()) { result, element in
var result = result
guard let first = element.first else { return result }
let initial = String(first)
result[initial] = (result[initial] ?? []) + [element]
return result
}
print(dictionary) // ["B": ["Brian", "Brittany"], "A": ["Aaron", "Adam"]]
Note that the result is not sorted. A dictionary is an unordered collection. If you need to sort your dictionary and return an array of (key, Value) tuples you can use sorted by key as follow:
let sorted = dictionary.sorted {$0.key < $1.key}
print(sorted)
"[(key: "A", value: ["Aaron", "Adam"]), (key: "B", value: ["Brian", "Brittany"])]\n"
Swift 4's new dictionary initializers can do it all for you:
let contactInitials = contacts.filter{!$0.isEmpty}.map{ ($0.first!,[$0]) }
let dict = [Character:[String]](contactInitials, uniquingKeysWith:+)
I'm trying to compare element to next element in a collection.
For example :
let array: [(Double, String)]= [(2.3, "ok"),
(1.4, "ok"),
(5.1, "notOk")]
I need a returned array who will summary element where the string is the same. So my result will be :
new array = [(3.7, "ok"), (5.1, "notOk")]
I need to do it functional if possible. i tried to get next element in a map but can't found how.
Something like this (this is just for logic, this code isn't working.
let newArray = array.map {(element, nextElement) in
if element.1 == nextElement.1 {
return element.0 + nextElement.0
}
}
In a more functional way:
let array: [(Double, String)]= [(2.3, "ok"),
(1.4, "ok"),
(5.1, "notOk")]
let keys = Set(array.map{$0.1}) // find unique keys
let result = keys.map { key -> (Double, String) in
let sum = array.filter {$0.1 == key} // find all entries with the current key
.map {$0.0} // map them to their values
.reduce(0, +) // sum the values
return (sum, key)
}
print(result)
Output:
[(5.0999999999999996, "notOk"), (3.6999999999999997, "ok")]
Alternatively (suggested by #dfri):
let keys = Set(array.map{$0.1}) // find unique keys
let result = keys.map { key -> (Double, String) in
let sum = array.reduce(0) { $0 + ($1.1 == key ? $1.0 : 0) }
return (sum, key)
}
I like alexburtnik's answer. It's basically word for word how I wrote my first pass of this. It's straightforward, clear, and efficient. It is excellent Swift.
But functional programming can help us think more deeply about problems and create better, reusable tools. So let's think functionally.
dfri's solution appears beautiful, but is O(m*n) (in the worst case, O(n^2)). It loops through the entire array for every unique key. This gets back the old adage by Alan Perlis: "A Lisp programmer knows the value of everything and the cost of nothing." But functional programming doesn't have to be inefficient.
The point of functional programming is to break down complex problems into simpler problems, make those simpler problems generic, and then recombine them. It's not about filters and flatMaps.
So let's break down this problem. We want to group by key, and then sum the values for each key. Grouping by key is going to be a lot easier if we sort by key first:
let result = array
.sorted(by: { $0.1 < $1.1 })
Now, we wish we could group them with something like this:
let result = array
.sorted(by: { $0.1 < $1.1 })
.grouped(by: { $0.1 == $1.1 })
I wish I had that grouped(by:). Wish fulfillment is the heart of functional programming, so let's write it. Well, a group is a sequence of elements that are all "equal" for some value of "equal." We could build that this way:
extension Array {
func grouped(by equal: (Element, Element) -> Bool) -> [[Element]] {
guard let firstElement = first else { return [] }
guard let splitIndex = index(where: { !equal($0, firstElement) } ) else { return [self] }
return [Array(prefix(upTo: splitIndex))] + Array(suffix(from: splitIndex)).grouped(by: equal)
}
That said, I don't really like this code. It's not very Swifty. That [Array(prefix(...)] + is a good indication of how much Swift hates us doing it this way. And it can be very expensive due to copying (probably getting us back to O(n^2). The Swiftier solution would be an Sequence:
struct GroupedSequence<Element>: Sequence, IteratorProtocol {
var elements: [Element]
let equal: (Element, Element) -> Bool
private var nextIndex = 0
init(of elements: [Element], by equal: #escaping (Element, Element) -> Bool) {
self.elements = elements
self.equal = equal
}
mutating func next() -> ArraySlice<Element>? {
guard nextIndex < elements.endIndex else { return nil }
let first = elements[nextIndex]
let splitIndex = elements[nextIndex..<elements.endIndex].index(where: { !equal($0, first) } ) ?? elements.endIndex
defer { nextIndex = splitIndex }
return elements[nextIndex..<splitIndex]
}
}
extension Array {
func grouped(by equal: #escaping (Element, Element) -> Bool) -> GroupedSequence<Element> {
return GroupedSequence(elements: self, equal: equal)
}
}
Yes, it mutates and it's a little more code, but it's also lazy (which is a key tool from functional programming), it's better Swift, and very reusable. I like it. But you can use the recursive, pure version if you like.
OK, so now we have an array of arrays that are equivalent. We want to map over those and reduce each element to its sum. So we'll have a reduce inside a map. But this is not O(n^2) because each reduce is only over a single slice. We're going to walk every element just one time. To take care of one impossible corner case (an empty group, which grouped(by:) will never actually create), we'll use flatMap, but it's really just a map. You might be tempted to jump to this, but don't do it:
let result: [(Double, String)] = array
.sorted(by: { $0.1 < $1.1 })
.grouped(by: { $0.1 == $1.1 })
.flatMap { group in
guard let key = group.first?.1 else { return nil }
return (group.reduce(0, { $0 + $1.0 }), // Sum of our values
key)
}
Why? That's horribly unreadable. This is what gives functional programming a bad name. What the heck is that last piece doing? No, we want functional composition, not just functional tools. So we extract a function:
func sumEach(pairGroup: ArraySlice<(Double, String)>) -> (Double, String)? {
guard let key = pairGroup.first?.1 else { return nil }
return (pairGroup.reduce(0, { $0 + $1.0 }), // Sum of our values
key)
}
Now, we can have our nice functional approach without sacrificing comprehension:
let result = array
.sorted(by: { $0.1 < $1.1 })
.grouped(by: { $0.1 == $1.1 })
.flatMap(sumEach(pairGroup:))
And in the process we've created a new tool, grouping, that we can use to compose other solutions. I think that's pretty nice.
But I'd still probably do it alexburtnik's way.
You can iterate over every tupple in your input array and save a sum in a dictionary like this:
let array: [(Double, String)] = [(1.0,"notok"),(2.0,"ok"),(3.0,"ok"),(4.0,"ok"),(5.0,"ok"),(6.0,"ok"), (7.0,"notok")]
var dict = [String: Double]()
for (value, key) in array {
dict[key] = (dict[key] ?? 0) + value
}
print ("dict: \(dict)")
Output:
dict: ["notok": 8.0, "ok": 20.0]
If you really need to get an array of tuples, use this:
let result = dict.map { (key, value) in (value, key) }
print ("result: \(result)")
Output:
result: [(8.0, "notok"), (20.0, "ok")]
I guess that a solution that makes a good use of Swift's features would be to combine filter and reduce:
let array: [(String, Double)] = [("ok", 2.4),
("ok", 1.3),
("not ok", 4.4),
("very not ok", 99.0)]
let key = "ok"
let result = array.filter({$0.0 != key}) + [array.filter({ $0.0 == key }).reduce((key, 0.0), { (key, $0.1 + $1.1) })]
print(result)
And then the result would be
[("not ok", 4.4000000000000004), ("very not ok", 99.0), ("ok", 3.7000000000000002)]
Which I assume is what you wanted to achieve.
EDIT:
To reduce all tuples you could simply wrap the solution inside of a function:
func reduceAllTuples(tupleArray: [(String, Double)]) -> [(String, Double)]{
var array = tupleArray
for (key, _) in tupleArray {
array = array.filter({$0.0 != key}) + [array.filter({ $0.0 == key }).reduce((key, 0.0), { (key, $0.1 + $1.1) })]
}
return array
}
I've looked through the methods here but I can't quite find what I'm looking for. I'm new-ish to Swift. I would like to extract a subset from a Dictionary based on a Set of key values, preferably without a loop.
For example, if my key Set is of type Set<String> and I have a Dictionary of type Dictionary<String, CustomObject>, I would like to create a new Dictionary of type Dictionary<String, CustomObject> that contains only the key-value pairs associated with the keys in the Set of Strings.
I can see that I could do this with for loop, by initializing a new Dictionary<String, CustomObj>(), checking if the original Dictionary contains a value at each String in the set, and adding key-value pairs to the new Dictionary. I am wondering if there is a more efficient/elegant way to do this however.
I'd be open to finding the subset with an Array of Strings instead of a Set if there is a better way to do it with an Array of keys.
Many thanks!
Swift 5 - You can do this very simply:
let subsetDict = originalDict.filter({ mySet.contains($0.key)})
The result is a new dictionary with the same type as the original but which only contains the key-value pairs corresponding to the keys in mySet.
Your assumption is correct, there is a more concise/swift-ish way to accomplish what you need.
For example you can do it via reduce, a functional programming concept available in Swift:
let subDict = originalDict.reduce([String: CustomObject]()) {
guard mySet.contains($1.key) else { return $0 }
var d = $0
d[$1.key] = $1.value
return d
}
Or, in two steps, first filtering the valid elements, and then constructing back the dictionary with the filtered elements:
let filteredDict = originalDict.filter { mySet.contains($0.key) }
.reduce([CustomObject]()){ var d = $0; d[$1.key]=$1.value; return d }
forEach can also be used to construct the filtered dictionary:
var filteredDict = [CustomObject]()
mySet.forEach { filteredDict[$0] = originalDict[$0] }
, however the result would be good it it would be immutable:
let filteredDict: [String:CustomObject] = {
var result = [String:CustomObject]()
mySet.forEach { filteredDict2[$0] = originalDict[$0] }
return result
}()
Dummy type:
struct CustomObject {
let foo: Int
init(_ foo: Int) { self.foo = foo }
}
In case you'd like to mutate the original dictionary (instead of creating a new one) in an "intersect" manner, based on a given set of keys:
let keySet = Set(["foo", "baz"])
var dict = ["foo": CustomObject(1), "bar": CustomObject(2),
"baz": CustomObject(3), "bax": CustomObject(4)]
Set(dict.keys).subtracting(keySet).forEach { dict.removeValue(forKey: $0) }
print(dict) // ["foo": CustomObject(foo: 1), "baz": CustomObject(foo: 3)]
I know that this topic has been already discussed but I can't solve looking other answers, so sorry in advance for my ripetion!
I need to sort this Dictionary by keys
codeValueDict = ["us": "$", "it": "€", "fr": "€"]
so I need a dictionary like this
sortedDict = ["fr": "€", "it": "€", "us": "$"]
but I can't do that.
I tried this
let sortedKeysAndValues = sorted(dictionary) { $0.0 < $1.0 }
but after I need to create two arrays from this dictionary (keys and values) and, using that solution
codesArray = sortedKeysAndValues.keys.array
give me the error '[(String, String)]' does not have a member named 'keys' because that solution doesn't return exactly a dictionary.
So i tried another solution:
let prova = codiceNomeDict as NSDictionary
for (k,v) in (Array(codiceNomeDict).sorted {$0.1 < $1.1}) {
let value = "[\"\(k)\": \"\(v)\"]"
println(value)
}
Which works good but then I don't know how create a new dictionary of values.
What's the best solution? How to make it works?
The output of sorted function above is an Array. So you cannot get keys & values like a Dictionary. But you can use map function to retrieve those sorted keys & values
Return an Array containing the sorted elements of source{according}.
The sorting algorithm is not stable (can change the relative order of
elements for which isOrderedBefore does not establish an order).
let codeValueDict = ["us": "$", "it": "€", "fr": "€"]
let sortedArray = sorted(codeValueDict, {$0.0 < $1.0})
print(sortedArray)
let keys = sortedArray.map {return $0.0 }
print(keys)
let values = sortedArray.map {return $0.1 }
print(values)
Dictionaries are not ordered. If you want to enumerate over them in order, you can do that using #HoaParis's solution (which is my preference), or also
for (k,v) in sorted(codiceNomeDict, {$0.1 < $1.1}) { ... }
which is just a little better way than what you were doing before because it doesn't generate a temporary array.
But if you really want "a collection that maps one value to another and is ordered by its key" then you need to create some other data structure for that. So let's do that. It's a good learning experience.
This version just implements SequenceType and provides a get/set subscript, which is most of what you'd generally want. Making it a full CollectionType is a bit of a pain I think, since startIndex and endIndex hae to be O(1). Possible; just more than I want to do this morning.
Note the major addition of Key: Comparable. That's why Dictionary can't be ordered. There's no promise that you can sort their keys. By adding that requirement, we can.
struct SortedDictionary<Key: Hashable, Value where Key: Comparable>: SequenceType {
private var dict: Dictionary<Key, Value>
init(_ dict: Dictionary<Key, Value>) {
self.dict = dict
}
func generate() -> GeneratorOf<(Key, Value)> {
let values = Array(zip(self.dict.keys, self.dict.values))
.sorted {$0.0 < $1.0 }
return GeneratorOf(values.generate())
}
subscript(key: Key) -> Value? {
get { return self.dict[key] }
set(value) { self.dict[key] = value }
}
}
var codeValueDict = ["us": "$", "it": "€", "fr": "€"]
var sortedDict = SortedDictionary(codeValueDict)
for (k, v) in sortedDict {
println("\(k) => \(v)")
}
sortedDict["us"]
sortedDict["ab"] = "!"
sortedDict
Why would you bother with SortedDictionary when you already have sorted()? Well, usually I wouldn't. But it does offer opportunities for abstraction. You could control sort order at object creation rather than at object enumeration. You could potentially cache the sort order (though I suspect in most cases that will hurt rather than help).
But I recommend just using sorted in general.
Swift doesn't include a sorted dictionary type, and dictionaries cannot be sorted. You could add an extension that offers sorting to [(Key, Value)] by doing this:
extension Dictionary {
func sort(isOrderedBefore: (Key, Key) -> Bool) -> [(Key, Value)] {
var result: [(Key, Value)] = []
let sortedKeys = keys.array.sorted(isOrderedBefore)
for key in sortedKeys {
result.append(key, self[key]!)
}
return result
}
}
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.
You can't sort dictionary in such easy way. I think dictionary is using some sort of tree data structure. But after sorting you get an array of tuples. So you can get keys in such way:
let codeValueDict = ["us": "$", "it": "€", "fr": "€"]
let sortedKeysAndValues = sorted(codeValueDict) { $0.0 < $1.0 }
let keys = sortedKeysAndValues.map {$0.0 }
let values = sortedKeysAndValues.map {$0.1 }
Sort keys case-insensitive in Swift 2
Here is a function which returns a case-insensitive sorted array of keys (or any String values).
Please keep in mind that Swift’s dictionary data structure can not be stored sorted by keys in memory. So yes, you can sort it by keys but if you print it for example then the key order is again random.
/// returns an array of values sorted by values case-insensitive
func sortCaseInsensitive(values:[String]) -> [String]{
let sortedValues = values.sort({ (value1, value2) -> Bool in
if (value1.lowercaseString < value2.lowercaseString) {
return true
} else {
return false
}
})
return sortedValues
}
Call with
let dict = ["world": "Hello!", "foo": "bar", "zYeah": "a", "akey": "xval"]
let sortedKeys = sortCaseInsensitive(Array(dict.keys))