How to use filteredArrayUsingPredicate with NSDictionary? - ios

Now I'm using
let array = (displayNames as NSArray).filteredArrayUsingPredicate(searchPredicate)
where displayNames is
var displayNames[String]()
But I want to use it with:
var displayNames[String: UIImage]()
How can I use .filteredArrayUsingPredicate with displayNames string part in NSDictionary?

Get the keys from the dictionary using the allKeys property and perform the filter on that. Try that.
RE-EDIT: Try something like this
let theOriginalDictionary = [String : UIImage]()
let searchPredicate = NSPredicate(format: "", argumentArray: nil)
let otherDictionary = NSDictionary(dictionary: theOriginalDictionary)
let arrayOfKeys = NSArray(array: otherDictionary.allKeys)
let filteredArray = arrayOfKeys.filteredArrayUsingPredicate(searchPredicate)

If you're using Swift, why don't you use built-in functions like filter?
var displayNames = [String: UIImage]()
let result = displayNames.filter {key,image in
return true // here add your predicate to filter, true means returning all
}

It sounds like you're trying to filter the dictionary, while keeping the dictionary type after the filter. There's a few ways to do this, but maybe the easiest is to extend dictionary:
extension Dictionary {
func filter(#noescape includeElement: (Key, Value) throws -> Bool) rethrows -> [Key:Value] {
var result: [Key:Value] = [:]
for (k,v) in self where try includeElement(k,v) {
result[k] = v
}
return result
}
}
Then, if you wanted to filter a dictionary based on the keys, you can do something like this:
let dict = ["a": 1, "b": 2, "c": 3]
let filtered = dict.filter { (k,_) in k != "a" }
// ["b": 2, "c": 3]

Related

Swift 1D Dictionary to 2D

I've got a little problem in understanding two-dimensional dictionaries. My function has to return dictionary for UITableView with sections. 1 template type can have multiple template strings. So when in fetchedData there are 2 or more texts with similar types, they have to be in array [String] with 1 key - String.
The code below is absolutely correct from the complier point of view. As for me smth is wrong, but nice auto-completions make me think that everything is OK.
Obviously it returns an empty dictionary [:]
func fetchTemplates() -> Dictionary<String, [String]> {
var templates: Dictionary<String, [String]> = [:]
let fetchRequest: NSFetchRequest<Template> = Template.fetchRequest()
fetchRequest.sortDescriptors = [SortDescriptor.init(key: "templateType", ascending: true)]
let fetchedData = try! context.fetch(fetchRequest)
if (!fetchedData.isEmpty) {
for templateItem in fetchedData {
templates[templateItem.templateType!]?.append(templateItem.templateText!)
}
return templates
}
else {
return templates
}
}
P.S. fetchedData returns:
<Template: 0x003281h4> (entity: Template; id: 0x003281h4 <x-coredata:///Template/> ; data: {
templateText = "Example";
templateType = "First";
})
The issue lies on this line:
templates[templateItem.templateType!]?.append(templateItem.templateText!)
templates was initialized with this line: var templates: Dictionary<String, [String]> = [:]. At this point, templates is an empty dictionary.
Let's break that line down into the steps that happen, in chronological order:
templateItem.templateType is accessed, and force unwrapped. A crash will occur if it's nil.
templateItem.templateType! is used as a key into the templates dictionary. This will always return nil. The dictionary is empty, thus is has no values for any keys, including this one.
?.append() is called, on the condition that it's not being called on nil. If it's called on nil, nothing will happen.
3 is the cause of your issue. You need to initialize a new array if one doesn't exist for the key yet:
func fetchTemplates() -> Dictionary<String, [String]> {
var templates: Dictionary<String, [String]> = [:]
let fetchRequest: NSFetchRequest<Template> = Template.fetchRequest()
fetchRequest.sortDescriptors = [SortDescriptor.init(key: "templateType", ascending: true)]
let fetchedData = try! context.fetch(fetchRequest)
if (!fetchedData.isEmpty) { //see note 2
for templateItem in fetchedData {
let type = templateItem.templateType!
var array = templates[type] ?? [] //see note 1
array!.append(templateItem.templateText!)
templates[type] = array
}
return templates
}
else {
return templates
}
}
This function can be simplified:
func fetchTemplates() -> [String : [String]] {
let fetchRequest = Template.fetchRequest()
fetchRequest.sortDescriptors = [SortDescriptor(key: "templateType", ascending: true)]
let fetchedData = try! context.fetch(fetchRequest)
var templates = [String, [String]]()
for templateItem in fetchedData {
let type = templateItem.templateType!
templates[type] = (templates[text] ?? []) + [templateItem.templateText!]
}
return templates
}
and reduce can be used instead:
func fetchTemplates() -> [String : [String]] { //see note 3
let fetchRequest = Template.fetchRequest() //see note 4
fetchRequest.sortDescriptors = [SortDescriptor(key: "templateType", ascending: true)] //see note 5
let fetchedData = try! context.fetch(fetchRequest)
return fetchedData.reduce([String, [String]]()){templates, templateItem in
(templates[templateItem.tempalteText!] ?? []) + [templateItem.templateText!]
} //see note 6
}
Notes
if template[text] is not nil, it's assigned to array. Otherwise, a new array ([]) is assigned to `array.
this check is unnecessary
Dictionary<String, [String]> can be written as just [String : [String]]
No need for an explicit type signiture
X.init() can be written as just X()
The emptiness check is unnecessary, and the whole for loop can be changed into a reduce call.
The issue is:
templates[templateItem.templateType!] is always nil because the dictionary is empty.
Therefore nothing can be appended.

swift NSDictionary filter using string

What I need is how we need to filter NSDictionary and return only the values and the keys where the key contain a string
For example if we have NSDictionary that contain :
{
"houssam" : 3,
"houss" : 2,
"other" : 5
}
and the string is "houss"
so we need to return
{
"houssam" : 3,
"houss" : 2
}
Best Regards,
You can use the filter function to get what you need like in the following way:
var dict: NSDictionary = ["houssam": 3, "houss": 2, "other": 5 ]
let string = "houss"
var result = dict.filter { $0.0.containsString(string)}
print(result) //[("houssam", 3), ("houss", 2)]
The above code return a list of tuples, if you want to get a [String: Int] dictionary again you can use the following code:
var newData = [String: Int]()
for x in result {
newData[x.0 as! String] = x.1 as? Int
}
print(newData) //["houssam": 3, "houss": 2]
I hope this help you.
Use this code to get matching keys.
var predicate = NSPredicate(format: "SELF like %#", "houss");
let matchingKeys = dictionary.keys.filter { predicate.evaluateWithObject($0) };
Then just fetch entries which keys are in matchingKeys array.
Since this is an NSDictionary, you can use the filteredArrayUsingPredicate: method on the array of keys, and fetch back the values from the initial dictionary.
For instance:
let data: NSDictionary = // Your NSDictionary
let keys: NSArray = data.allKeys
let filteredKeys: [String] = keys.filteredArrayUsingPredicate(NSPredicate(format: "SELF CONTAINS[cd] %#", "houss")) as! [String]
let filteredDictionary = data.dictionaryWithValuesForKeys(filteredKeys)
Hope that helps.

Swift filter dictionary error: Cannot assign a value of type '[(_, _)]' to a value of type '[_ : _]'

I am trying to filter a dictionary in swift:
var data: [String: String] = [:]
data = data.filter { $0.1 == "Test" }
the filter code above compiles under Swift 2 but yields the following error:
Cannot assign a value of type '[(String, String)]' to a value of type '[String : String]'
is this a bug in the Swift compiler or is this not the right way to filter dictionaries in Swift?
This has been fixed in Swift 4
let data = ["a": 0, "b": 42]
let filtered = data.filter { $0.value > 10 }
print(filtered) // ["b": 42]
In Swift 4, a filtered dictionary returns a dictionary.
Original answer for Swift 2 and 3
The problem is that data is a dictionary but the result of filter is an array, so the error message says that you can't assign the result of the latter to the former.
You could just create a new variable/constant for your resulting array:
let data: [String: String] = [:]
let filtered = data.filter { $0.1 == "Test" }
Here filtered is an array of tuples: [(String, String)].
Once filtered, you can recreate a new dictionary if this is what you need:
var newData = [String:String]()
for result in filtered {
newData[result.0] = result.1
}
If you decide not to use filter you could mutate your original dictionary or a copy of it:
var data = ["a":"Test", "b":"nope"]
for (key, value) in data {
if value != "Test" {
data.removeValueForKey(key)
}
}
print(data) // ["a": "Test"]
Note: in Swift 3, removeValueForKey has been renamed removeValue(forKey:), so in this example it becomes data.removeValue(forKey: key).
data.forEach { if $1 != "Test" { data[$0] = nil } }
Just another approach (a bit simplified) to filter out objects in your dictionary.
Per Apple docs, filter:
Returns an array containing, in order, the elements of the sequence that satisfy the given predicate.
https://developer.apple.com/reference/swift/sequence/1641239-filter
I found myself needing to do what the OP was asking about and ended up writing the following extensions (Swift 3):
extension Dictionary
{
func filteredDictionary(_ isIncluded: (Key, Value) -> Bool) -> Dictionary<Key, Value>
{
return self.filter(isIncluded).toDictionary(byTransforming: { $0 })
}
}
extension Array
{
func toDictionary<H:Hashable, T>(byTransforming transformer: (Element) -> (H, T)) -> Dictionary<H, T>
{
var result = Dictionary<H,T>()
self.forEach({ element in
let (key,value) = transformer(element)
result[key] = value
})
return result
}
}
Usage:
let data = ["a":"yes", "b":"nope", "c":"oui", "d":"nyet"]
let filtered = data.filteredDictionary({ $0.1 >= "o" })
// filtered will be a dictionary containing ["a": "yes", "c": "oui"]
I've found this method to be useful after filtering or applying some other transform that results in an array of dictionary elements:
extension Array {
func dictionary<K: Hashable, V>() -> [K: V] where Element == Dictionary<K, V>.Element {
var dictionary = [K: V]()
for element in self {
dictionary[element.key] = element.value
}
return dictionary
}
}
To use it, just say something like:
dictionary = dictionary.filter{ $0.key == "test" }.dictionary()
The advantage of this method is that no argument of any kind needs to be passed to the dictionary() method. Generic type arguments tell the compiler everything it needs to know.
You can arrange ascending order according to dictionary value using filter
let arrOfDict = [{"ABC":24},{"XYZ":21},{"AAA":23}]
let orderedDict = arrOfDict.filter{$0.value < $1.value}
you will get below output:
[
{ "XYZ": 21 },
{ "AAA": 23 },
{ "ABC": 24 }
]

Swift array of dictionaries with multiple types without using NSDictionary in iOS

I have been working with an array of dictionaries in Swift (version 7 beta 4) using NSDictionary. The array is a collection of fmdb results which contain differing data types. I would like to end up with an array of native swift dictionaries in order to use the filter functionality of swift collection types. The following is a snippet of the query function I'm using the create the array:
class func query(sql:String) -> [NSDictionary] {
let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask, true)
let docsDir = dirPaths[0] as String
let databasePath:String = docsDir.stringByAppendingPathComponent("myDB.db")
let db = FMDatabase(path: databasePath as String)
var resultsArray:[NSDictionary] = []
if db.open() {
let results:FMResultSet? = db.executeQuery(sql, withArgumentsInArray: nil)
while (results?.next() == true) {
resultsArray.append(results!.resultDictionary()) //appending query result
}
db.close()
} else {
print("Error: \(db.lastErrorMessage())")
}
return resultsArray
}
I tried using AnyObject such as:
class func query(sql:String) -> [[String:AnyObject]] {
let dirPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask, true)
let docsDir = dirPaths[0] as String
let databasePath:String = docsDir.stringByAppendingPathComponent("Kerbal.db")
let db = FMDatabase(path: databasePath as String)
var resultsArray:[[String:AnyObject]] = []
if db.open() {
let results:FMResultSet? = db.executeQuery(sql, withArgumentsInArray: nil)
while (results?.next() == true) {
resultsArray.append(results!.resultDictionary() as! Dictionary<String,AnyObject>)
}
db.close()
} else {
print("Error: \(db.lastErrorMessage())")
}
return resultsArray
}
however I can't use the filter such as resultsArray.filter({$0["fieldName"] == "part"}) with AnyObject.
My question: Is it possible to create this array with native Swift dictionaries even though the dictionaries have different types? Could the new protocol extension be used on collection type to solve this problem?
Any suggestions are appreciated.
If you don't know which objects you are dealing with you have to use filter like so:
resultsArray.filter{ ($0["fieldName"] as? String) == "part"}
So you optionally cast the value to the desired type and compare it.
Note: I'm using the trailing closure syntax.
As suggestion I would use a tuple of different arrays which hold dictionaries:
// some dummy values
let resultsArray: [[String : AnyObject]] = [["Hi" : 3], ["Oh" : "String"]]
var result = (doubles: [[String : Double]](), strings: [[String : String]]())
// going through all dictionaries of type [String : AnyObject]
for dict in resultsArray {
// using switch and pattern matching to cast them (extensible for more types)
// you have to up cast the dictioanary in order to use patten matching
switch dict as AnyObject {
case let d as [String : Double]: result.doubles.append(d)
case let s as [String : String]: result.strings.append(s)
default: fatalError("unexpected type")
}
}
return result
You should probably adopt the Equatable protocol, not all AnyObjects are such in fact, and so no comparison and thereafter filtering may be done.

Array from dictionary keys in swift

Trying to fill an array with strings from the keys in a dictionary in swift.
var componentArray: [String]
let dict = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("Components", ofType: "plist")!)
componentArray = dict.allKeys
This returns an error of: 'AnyObject' not identical to string
Also tried
componentArray = dict.allKeys as String
but get: 'String' is not convertible to [String]
Swift 3 & Swift 4
componentArray = Array(dict.keys) // for Dictionary
componentArray = dict.allKeys // for NSDictionary
With Swift 3, Dictionary has a keys property. keys has the following declaration:
var keys: LazyMapCollection<Dictionary<Key, Value>, Key> { get }
A collection containing just the keys of the dictionary.
Note that LazyMapCollection that can easily be mapped to an Array with Array's init(_:) initializer.
From NSDictionary to [String]
The following iOS AppDelegate class snippet shows how to get an array of strings ([String]) using keys property from a NSDictionary:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let string = Bundle.main.path(forResource: "Components", ofType: "plist")!
if let dict = NSDictionary(contentsOfFile: string) as? [String : Int] {
let lazyMapCollection = dict.keys
let componentArray = Array(lazyMapCollection)
print(componentArray)
// prints: ["Car", "Boat"]
}
return true
}
From [String: Int] to [String]
In a more general way, the following Playground code shows how to get an array of strings ([String]) using keys property from a dictionary with string keys and integer values ([String: Int]):
let dictionary = ["Gabrielle": 49, "Bree": 32, "Susan": 12, "Lynette": 7]
let lazyMapCollection = dictionary.keys
let stringArray = Array(lazyMapCollection)
print(stringArray)
// prints: ["Bree", "Susan", "Lynette", "Gabrielle"]
From [Int: String] to [String]
The following Playground code shows how to get an array of strings ([String]) using keys property from a dictionary with integer keys and string values ([Int: String]):
let dictionary = [49: "Gabrielle", 32: "Bree", 12: "Susan", 7: "Lynette"]
let lazyMapCollection = dictionary.keys
let stringArray = Array(lazyMapCollection.map { String($0) })
// let stringArray = Array(lazyMapCollection).map { String($0) } // also works
print(stringArray)
// prints: ["32", "12", "7", "49"]
Array from dictionary keys in Swift
componentArray = [String] (dict.keys)
You can use dictionary.map like this:
let myKeys: [String] = myDictionary.map{String($0.key) }
The explanation:
Map iterates through the myDictionary and accepts each key and value pair as $0. From here you can get $0.key or $0.value. Inside the trailing closure {}, you can transform each element and return that element. Since you want $0 and you want it as a string then you convert using String($0.key). You collect the transformed elements to an array of strings.
dict.allKeys is not a String. It is a [String], exactly as the error message tells you (assuming, of course, that the keys are all strings; this is exactly what you are asserting when you say that).
So, either start by typing componentArray as [AnyObject], because that is how it is typed in the Cocoa API, or else, if you cast dict.allKeys, cast it to [String], because that is how you have typed componentArray.
extension Array {
public func toDictionary<Key: Hashable>(with selectKey: (Element) -> Key) -> [Key:Element] {
var dict = [Key:Element]()
for element in self {
dict[selectKey(element)] = element
}
return dict
}
}
dict.keys.sorted()
that gives [String]
https://developer.apple.com/documentation/swift/array/2945003-sorted
From the official Array Apple documentation:
init(_:) - Creates an array containing the elements of a sequence.
Declaration
Array.init<S>(_ s: S) where Element == S.Element, S : Sequence
Parameters
s - The sequence of elements to turn into an array.
Discussion
You can use this initializer to create an array from any other type that conforms to the Sequence protocol...You can also use this initializer to convert a complex sequence or collection type back to an array. For example, the keys property of a dictionary isn’t an array with its own storage, it’s a collection that maps its elements from the dictionary only when they’re accessed, saving the time and space needed to allocate an array. If you need to pass those keys to a method that takes an array, however, use this initializer to convert that list from its type of LazyMapCollection<Dictionary<String, Int>, Int> to a simple [String].
func cacheImagesWithNames(names: [String]) {
// custom image loading and caching
}
let namedHues: [String: Int] = ["Vermillion": 18, "Magenta": 302,
"Gold": 50, "Cerise": 320]
let colorNames = Array(namedHues.keys)
cacheImagesWithNames(colorNames)
print(colorNames)
// Prints "["Gold", "Cerise", "Magenta", "Vermillion"]"
Swift 5
var dict = ["key1":"Value1", "key2":"Value2"]
let k = dict.keys
var a: [String]()
a.append(contentsOf: k)
This works for me.
NSDictionary is Class(pass by reference)
Dictionary is Structure(pass by value)
====== Array from NSDictionary ======
NSDictionary has allKeys and allValues get properties with
type [Any].
let objesctNSDictionary =
NSDictionary.init(dictionary: ["BR": "Brazil", "GH": "Ghana", "JP": "Japan"])
let objectArrayOfAllKeys:Array = objesctNSDictionary.allKeys
let objectArrayOfAllValues:Array = objesctNSDictionary.allValues
print(objectArrayOfAllKeys)
print(objectArrayOfAllValues)
====== Array From Dictionary ======
Apple reference for Dictionary's keys and values properties.
let objectDictionary:Dictionary =
["BR": "Brazil", "GH": "Ghana", "JP": "Japan"]
let objectArrayOfAllKeys:Array = Array(objectDictionary.keys)
let objectArrayOfAllValues:Array = Array(objectDictionary.values)
print(objectArrayOfAllKeys)
print(objectArrayOfAllValues)
This answer will be for swift dictionary w/ String keys. Like this one below.
let dict: [String: Int] = ["hey": 1, "yo": 2, "sup": 3, "hello": 4, "whassup": 5]
Here's the extension I'll use.
extension Dictionary {
func allKeys() -> [String] {
guard self.keys.first is String else {
debugPrint("This function will not return other hashable types. (Only strings)")
return []
}
return self.flatMap { (anEntry) -> String? in
guard let temp = anEntry.key as? String else { return nil }
return temp }
}
}
And I'll get all the keys later using this.
let componentsArray = dict.allKeys()
// Old version (for history)
let keys = dictionary.keys.map { $0 }
let keys = dictionary?.keys.map { $0 } ?? [T]()
// New more explained version for our ducks
extension Dictionary {
var allKeys: [Dictionary.Key] {
return self.keys.map { $0 }
}
}

Resources