How to sort an array of Structures with/by dynamic property - swift

Given an NSTableView that has an array of structures as its datasource. A user can click on any column heading to sort by that column. The column identifiers match the property names of the properties within the structure.
Given a structure
struct MyStructure {
var col0data = "" //name matches the column identifier
var col1data = ""
}
and an array of structures
var myArray = [MyStructure]()
The goal is that when a column heading is clicked, use that column's identifier to sort the array of structures by that column identifier/property
With an array of dictionaries, it was easy...
self.myArrayOfDictionaries.sortInPlace {
(dictOne, dictTwo) -> Bool in
let d1 = dictOne[colIdentifier]! as String;
let d2 = dictTwo[colIdentifier]! as String;
return d1 < d2 //or return d1 > d2 for reverse sort
}
The question is how to access the properties of the Structure dynamically, something like
let struct = myArray[10] as! MyStructure //get the 10th structure in the array
let value = struct["col0data"] as! String //get the value of the col0data property
If there is a better way, suggestions would be appreciated.
I should also note that the structure may have 50 properties so this is an effort to reduce the amount of code needed to sort the array by any one of those properties.
edit:
One solution is to change the structure to a class derived from NSObject. Then the properties could be accessed via .valueForKey("some key"). However, I am trying to keep this Swifty.

Maybe I have a solution to your problem. The advantage of this code over your solution is here you don't need to add a subscript method to your struct to create an hardcoded String-Property-Value map via code.
Here's my extension
extension _ArrayType {
func sortedBy(propertyName propertyName: String) -> [Self.Generator.Element] {
let mirrors = self.map { Mirror(reflecting: $0) }
let propertyValues = mirrors.map { $0.children.filter { $0.label == propertyName }.first?.value }
let castedValues = propertyValues.map { $0 as? String }
let sortedArray = zip(self, castedValues).sort { (left, right) -> Bool in
return left.1 < right.1
}.map { $0.0 }
return sortedArray
}
}
Usage
struct Animal {
var name: String
var type: String
}
let animals = [
Animal(name: "Jerry", type: "Mouse"),
Animal(name: "Tom", type: "Cat"),
Animal(name: "Sylvester", type: "Cat")
]
animals.sortedBy(propertyName: "name")
// [{name "Jerry", type "Mouse"}, {name "Sylvester", type "Cat"}, {name "Tom", type "Cat"}]
animals.sortedBy(propertyName: "type")
// [{name "Tom", type "Cat"}, {name "Sylvester", type "Cat"}, {name "Jerry", type "Mouse"}]
Limitations
The worst limitation of this solutions is that it works only for String properties. It can be change to work with any types of property by it must be at compile time. Right now I have not a solution to make it work with any king of property type without changing the code.
I already asked help for the core of the problem here.

I would definitely recommend simply embedding your dictionary into your struct. A dictionary is a much more suitable data structure for 50 key-value pairs than 50 properties – and you've said that this would be an acceptable solution.
Embedding the dictionary in your struct will give you the best of both worlds – you can easily encapsulate logic & you have have easy lookup of the values for each column ID.
You can now simply sort your array of structures like this:
struct MyStructure {
var dict = [String:String]()
init(col0Data:String, col1Data:String) {
dict["col0data"] = col0Data
dict["col1data"] = col1Data
}
}
var myArray = [MyStructure(col0Data: "foo", col1Data: "bar"), MyStructure(col0Data: "bar", col1Data: "foo")]
var column = "col0data"
myArray.sort {
$0.dict[column] < $1.dict[column]
}
print(myArray) // [MyStructure(dict: ["col0data": "bar", "col1data": "foo"]), MyStructure(dict: ["col0data": "foo", "col1data": "bar"])]
column = "col1data"
myArray.sort {
$0.dict[column] < $1.dict[column]
}
print(myArray) // MyStructure(dict: ["col0data": "foo", "col1data": "bar"])], [MyStructure(dict: ["col0data": "bar", "col1data": "foo"])

Here's an answer (but not the best answer); use subscripts to return the correct property, and set which property you are sorting by within the array.sort:
struct MyStructure {
var col0data = "" //name matches the column identifier
var col1data = ""
subscript(key: String) -> String? { //the key will be the col identifier
get {
if key == "col0data" {
return col0data
} else if key == "col1data" {
return col1data
}
return nil
}
}
}
And then here's how the sort works:
let identifier = the column identifier string,say col0data in this case
myArray.sortInPlace ({
let my0 = $0[identifier]! //the identifier from the table col header
let my1 = $1[identifier]!
return my0 < my1
})

If you do not know what types the values of MyStructure can be you will have a hard time comparing them to sort them. If you had a function that can compare all types you can have in MyStructure then something like this should work
struct OtherTypeNotComparable {
}
struct MyStructure {
var col0data = "cat" //name matches the column identifier
var col1data: OtherTypeNotComparable
}
let structures = [MyStructure(), MyStructure()]
let sortBy = "col1data"
func yourCompare(a: Any, b: Any) -> Bool {
return true
}
var expanded : [[(String, Any, MyStructure)]]
= structures.map { s in Mirror(reflecting: s).children.map { ($0!, $1, s) } }
expanded.sortInPlace { (a, b) -> Bool in
let aMatch = a.filter { $0.0 == sortBy }.first!.1
let bMatch = b.filter { $0.0 == sortBy }.first!.1
return yourCompare(aMatch, b: bMatch)
}
source: https://developer.apple.com/library/watchos/documentation/Swift/Reference/Swift_Mirror_Structure/index.html

Related

How can i extract the array of the objects in Swift?

i have json objects like and parsed in an array
let objects = [Object]()
struct Object {
name: String
id: Int
}
Suppose like
let objects [Object(name:oscar, id: 11), Object(name:sanchez, id: 12),Object(name:emily, id: 15),Object(name:clarck, id: 31) ... ]
How can i take the string array as below also with this name which object belongs to ? ( so i can use object easily)
let stringPropertyArray = [oscar, sanchez,emily,clarck ... ]
Thanks
how i will find the object ? if you have "emily" and i want to item.id which emily belongs to ?
Perhaps you want something like
if let ob = objects.first {$0.name == "emily"} {
print(ob.id)
}
But if your goal is to search quickly, it would be better to have a dictionary keyed by the value you will be searching on.
I think this is what you want
let stringPropertyArray: [String] = objects.map {$0.name}
There are 2 approaches you can use:
by looping (traditional approach)
var listName: [String] = []
for item in objects {
listName.append(item.name)
}
by using higher order function
let listName = objects.map{ $0.name }
There would be a case if your name property is optional and for some object, name property value is nil then we should use compactMap higher order function in order to avoid nil object in the list
let listName = objects.compactMap{ $0.name }
To find any specific object we can use filter like below:
let object = objects.filter{
$0.name == "sanchez" }.first
// OR
let object = objects.first { object -> Bool in
object.name == "emily" }

Swift 5 How can I hash two arrays

How can I make this code SWIFT accepting? I've got two arrays of type ANY one array's value should act as the key, the other one as the appropriate value:
let it_tt_ar = db.pair(keys: "int_test", values: "text_test");
func _pair<K : Hashable, V>(keys: [K], values: [V]) -> Dictionary<K,V> {
var result = Dictionary<K, V>();
for i in 0...(keys.count - 1) {
result[keys[i]] = values[i];
}
return result;
}
func pair (keys: String?, values: String?) -> Dictionary<Int32,Any> {
if let _keys = keys, let _values = values {
let result = _pair(keys: hashtable[_keys] as! [Int32], values: hashtable[_values]!);
return result;
} else {
return [:];
}
}
I can't get it working if the type of the key is unknown. I want to write it like this:
let it_tt_ar = db.pair<Int32,String>(keys: "int_test", values: "text_test");
or
let it_tt_ar = db.pair(keys: "int_test", values: "text_test", kt:(Int32.self,String.self));
... in the last case by catching kt: in the function
But there's seems no chance to win against SWIFT:
cannot specify generic functions
or
Int32 cannot fulfill the hashable protocol
It's terrible! You want to write application logic but 80% of the development time is wasted by got to have fulfill such rules!
It looks like you're trying to turn a pair of arrays into a dictionary, regardless of the type of the array (provided, of course, that the type of the key array element is hashable). Here is one way:
let k : [Int] = [1,2,3]
let v : [String] = ["one", "two", "three"]
func pair<Key, Value>(keyArray keys:[Key], valueArray values:[Value]) -> Dictionary<Key,Value> where Key:Hashable {
zip(keys,values).reduce(into: Dictionary<Key,Value>()) {
(dict, tuple) in dict[tuple.0] = tuple.1
}
}
let result = pair(keyArray: k, valueArray: v)
print(result) // [1: "one", 2: "two", 3: "three"], in some order
Found a solution that works for me:
var db = try DataBaseSqlite(dbfile: "test.db");
try db.select(sql: "int_test, real_test, text_test from stest");
var it = db.valueByKey(key: "int_test");
var rt = db.valueByKey(key: "real_test");
var tt = db.valueByKey(key: "text_test");
let it_tt_ar = db.pair(keys: "int_test", values: "text_test", kt: Int32.self);
let tt_it_ar = db.pair(keys: "text_test", values: "int_test", kt: String.self);
try db.close();
func _pair<K : Hashable, V>(keys: [K], values: [V]) -> Dictionary<K,V> {
var result = Dictionary<K, V>();
for i in 0...(keys.count - 1) {
result[keys[i]] = values[i];
}
return result;
}
func pair<T>(keys: String?, values: String?, kt: T.Type) -> Dictionary<T,Any> {
if let _keys = keys, let _values = values {
let result = _pair(keys: hashtable[_keys] as! [T], values: hashtable[_values]!);
return result;
} else {
return [:];
}
}
Due to lack of supporting a real hashtable in Swift (like c# does), my hashtable is just an Dictionary of <String,Array> which is automatically built up by the select method.
So from an application point of view I can write a more efficient and generic code to query sqlite databases.
dbValueByKey() returns a typed (requested) Array of the column values and pair() returns just a combination of two columns.

Delete an element in array of a structure in swift

EDIT:
I would like to delete one of the element in the array list of structure type
struct Folder {
let name:String
let menu:[String:String]
}
I have a variable of
section = Folder
I want to check that is there any value in menu[String:String] contain specific value or not and remove that element out
section.menu = ["hello" : "a","b","c"]
if there any value of hello == a {
remove it out
}
At the end
section.menu = ["hello" : "b","c"]
You can create mutating function like that removeMenu(forValue value: String)
struct Folder {
let name:String
var menu:[String:String]
mutating func removeMenu(forValue value: String) {
menu = menu.filter({ $0.value != value})
}
}
var section = Folder(name: "FolderName", menu: ["k1": "keyValue1", "k2": "keyValue2"])
section.removeMenu(forValue: "keyValue1")
print(section)
output:
//Folder(name: "FolderName", menu: ["k2": "keyValue2"])
So first of all you need to make menu an actual variable instead of a constant, and it needs to be a dictionary of Strings to Array of Strings.
Then, you can remove entries from the array easily by getting their index and calling remove:
struct Folder {
let name:String
var menu: [String: [String]]
}
var section = Folder(name: "foo", menu: [ "hello": ["a", "b", "c"]])
if let index = section.menu["hello"]?.firstIndex(of: "a") {
section.menu["hello"]?.remove(at: index)
}
print(section.menu) // ["hello": ["b", "c"]]

Swift dictionary all containing

Lets say I have dictionaries like below and wanted an array of red dogs. I figured I need to get an array of all the names of the type "dog" using the first dictionary, and then use the name key and the color to search the final dictionary to get ["Polly,"jake"]. I've tried using loops but can't figure out how to iterate through the dictionary.
var pets = ["Polly" : "dog", "Joey" : "goldfish", "Alex" : "goldfish", "jake" : "dog"]
var petcolor = ["Polly" : "red", "Joey" : "black", "Alex" : "yellow", "jake":red"]
The correct solution would seem to be to create a Pet struct (or class) and collate all of this information into a struct and build either an array or dictionary full of these values.
struct Pet {
let name: String
let type: String
let color: String
init(name: String, type: String, color: String) {
self.name = name
self.type = type
self.color = color
}
}
Now, let's build an array of these pets:
var goodPets = [Pet]()
for (petName, petType) in pets {
guard let petColor = petcolor[petName] else {
// Found this pet's type, but couldn't find its color. Can't add it.
continue
}
goodPets.append(Pet(name: petName, type: petType, color: petColor))
}
Now that we've filled out goodPets, pulling out any particular subset of Pets becomes very easy:
let redDogs = goodPets.filter { $0.type == "dog" && $0.color = "red" }
And although this answer looks like a lot of set up & legwork compared to other answers, the major advantage here is that once we build the goodPets array, any way we want to scoop pets out of there ends up being more efficient. And as we increase the number of properties the pets have, this becomes more and more true compared to the other answers.
If you'd rather store our model objects in a dictionary continuing to use the names as the keys, we can do that as well, but the filter looks a little bit stranger.
Building the dictionary looks mostly the same:
var goodPets = [String : Pet]()
for (petName, petType) in pets {
guard let petColor = petcolor[petName] else {
// Found this pet's type, but couldn't find its color. Can't add it.
continue
}
goodPets[petName] = (Pet(name: petName, type: petType, color: petColor))
}
But the filter is slightly different:
let redDogs = goodPets.filter { $0.1.type = "dog" && $0.1.color = "red" }
Note that in both cases, redDogs has the type [Pet], that is, an array of Pet values.
You can iterate through a dictionary like this:
for key in pets.keys() {
if pets[key] == "Dog" {
}
}
Or:
for (name, pet) in pets {
if pet == "Dog" {
}
}
nhgrif is probably correct about structure but, to answer the literal question:
let dogs = Set(pets.filter { $0.1 == "dog" }.map { $0.0 })
let redAnimals = Set(petscolor.filter { $0.1 == "red" }.map { $0.0 })
let redDogs = dogs.intersect(redAnimals)
Each filter is a block that operates on a (key, value) tuple, testing the value and ultimately creating a dictionary with only the matching (key, value) pairs. Each map then converts that filtered dictionary into an array by discarding the values and just keeping the keys.
Each array is turned into a set to support the intersect operation. The intersect then determines the intersection of the two results.

accessing inner dictionaries (dictionary of dictionaries) with one entry, when not knowing what key or value for that entry are

This is how I declared my dictionary:
var dataDictionary: [NSIndexPath:[Int:Bool]]!
The inner dictionary will always have just one entry e.g. [1:true]. When I search dataDictionary I specify a NSIndexPath. The inner dictionary gets returned. Sth like this:
var innerDictionary = dataDictionary[indexPath] // is of type [Int:Bool]
I would like to access inner dictionarys key and value, but I don't want to specify an Integer or Bool, because it is unknown. I only want to get that Int and/or Bool value stored in inner dictionary. No search of inner dictionary is needed, because if will always contains just one entry. Maybe something like this:
var key = innerDictionary.key
var value = innerDictionary.value
How can this be accomplished?
If your dictionary has only one entry, and you don't know the key, with Swift 2 you can get the first element of the keys sequence safely with if let:
let innerDictionary = [42: true]
if let k = innerDictionary.keys.first {
print(k) // prints 42
} else {
// dict is empty
}
Same for the value:
if let v = innerDictionary.values.first {
print(v) // prints true
} else {
// key has no value
}
Try by this way:
func nestedDic(){
var innerDictionary:NSDictionary = ["Item 1": "data 0", "Item 2": "data 1"]
//NSLog("original object:\(innerDictionary)")
NSLog("all keys array:\(innerDictionary.allKeys)")
if(innerDictionary.isKindOfClass(NSDictionary)){
for key in innerDictionary.allKeys{
let value = innerDictionary.valueForKey(key as! NSString as String)
NSLog("key value = \(value as! String)")
}
}
}
You could create a simple struct for the inner dictionary if there is only one entry, then you are able to use your favorite property names.
struct InnerDictionary {
var key = 0
var value = false
}
let indexPath = NSIndexPath(index: 1)
var dataDictionary = [indexPath: InnerDictionary(key: 1, value: true)]
if let innerDictionary = dataDictionary[indexPath] {
var key = innerDictionary.key
var value = innerDictionary.value
}