I have an array of structs that looks like this:
struct minStruct {
var namn:String?
var imag:UIImage?
var rea:String?
var comp:String?
}
var structArr = [minStruct]()
If I would like to remove a specific struct from that array, I could simply do this:
var oneStruct = minStruct(namn: "Name", imag: UIImage(named: "Image"), rea: "Something", comp: "Something")
if structArr.filter({$0.namn == oneStruct!.namn}).count > 0 {
structArr = structArr.filter({$0.namn != oneStruct!.namn})
}
However, what I would like to do is to remove an array of structs from structArr. Something like this:
structArr = [minStruct(namn: "Name", imag: UIImage(named: "Image"), rea: "Something", comp: "Something"), minStruct(namn: "secondName", imag: UIImage(named: "secondImage"), rea: "Something2", comp: "Something2"), minStruct(namn: "thirdName", imag: UIImage(named: "thirdImage"), rea: "Something3", comp: "Something3")]
var arrToDelete = [minStruct(namn: "Name", imag: UIImage(named: "Image"), rea: "Something", comp: "Something"), minStruct(namn: "secondName", imag: UIImage(named: "secondImage"), rea: "Something2", comp: "Something2")]
So what I want to do is to delete all the items that are inside of arrToDelete from arrStruct. In this example, arrToDelete contains two of the three structs that structArr contains. I want to delete these two structs and keep the one struct that arrToDelete did not contain. I hope that I was clear enough!
Hashable
So we have struct. First of all let's make it Hashable
struct Element: Hashable {
var name: String?
var image: UIImage?
var rea: String?
var comp: String?
var hashValue: Int { return name?.hashValue ?? image?.hashValue ?? rea.hashValue ?? comp.hashValue ?? 0 }
}
func ==(left:Element, right:Element) -> Bool {
return left.name == right.name && left.image == right.image && left.rea == right.rea && left.comp == right.comp
}
Data
Next we have these 2 arrays
let elms : [Element] = [
Element(name:"a", image:nil, rea:nil, comp:nil),
Element(name:"b", image:nil, rea:nil, comp:nil),
Element(name:"c", image:nil, rea:nil, comp:nil)
]
let shouldBeRemoved: [Element] = [
Element(name:"b", image:nil, rea:nil, comp:nil),
Element(name:"c", image:nil, rea:nil, comp:nil)
]
Solution #1
If you DO NOT care about the original sorting you can use
let filtered = Array(Set(elms).subtract(shouldBeRemoved))
Solution #2
If you DO care about the original sorting
let shouldBeRemovedSet = Set(shouldBeRemoved)
let filtered = elms.filter { !shouldBeRemovedSet.contains($0) }
Why didn't I just write this?
let filtered = elms.filter { !shouldBeRemoved.contains($0) }
The line above is a correct solution. However invoking contains on
Array is generally slower (usually n/2 checks need to be performed)
than invoking it on a Set (usually a single check).
Structs for some reason aren't really allowed to be compared using Boolean logic, thus it's hard to even search an array of Structs for an item or the item's index. So for instance, if you wanted to search an array of Structs for a specific struct and then delete it:
First, you'd need to go to your struct file and give it a protocol which allows the struct to be used in boolean logic, so...
struct Persons: Equatable {
struct properties...
struct init....
}
Then you could search within the array using array methods such as...
firstIndex(of: theStructYourLookingFor)
lastIndex(of: _)
or any other boolean-related array methods.
This could be newish as of Swift 4.0 and greater-- April 2019
Related
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"]]
Consider a situation where we want to have a dictionary of arrays, with each array being a homogeneous collection of values of some type (which may be a struct or a primitive type). I'm currently using the ObjectIdentifier of the type defining it thusly:
let pInts : [UInt32] = [4, 6, 99, 1001, 2032]
let pFloats : [Float] = [3.14159, 8.9]
let pBools : [Bool] = [true, false, true]
let myDataStructure : [ObjectIdentifier : [Any]] = [
ObjectIdentifier(Float.self) : pFloats,
ObjectIdentifier(UInt32.self) : pInts,
ObjectIdentifier(Bool.self) : pBools
]
The issue here is that when traversing the data structure, Swift doesn't know that the objects in each list are homogeneous. Since swift is statically typed, I'm guessing it is not possible to typecast the [Any] lists using the ObjectIdentifier keys. Consider this traversal pseudocode:
for (typeObjId, listOfValuesOfSometype) in myDataStructure {
// do something like swap values around in the array,
// knowing they are homogeneously but anonymously typed
}
So, is there some metatype machinery I can concoct to represent this data structure in a way that does not anticipate the list of actual types that will have arrays in it?
I'm not exactly sure what you want to accomplish, Inside the dictionary loop the arrays will always be of type Any, but if you want to move items in the arrays around, you could just do that. Just reassign the array first to a var and then put it back in the dictionary.
If you do want to loop through the items of a specific type, then you could use the array helper function below.
func testX() {
let pInts: [UInt32] = [4, 6, 99, 1001, 2032]
let pFloats: [Float] = [3.14159, 8.9]
let pBools: [Bool] = [true, false, true]
var myDataStructure: [ObjectIdentifier: [Any]] = [
ObjectIdentifier(Float.self): pFloats,
ObjectIdentifier(UInt32.self): pInts,
ObjectIdentifier(Bool.self): pBools
]
// Swap the first 2 items of every array
for d in myDataStructure {
var i = d.value
if i.count > 1 {
let s = i[0]
i[0] = i[1]
i[1] = s
}
myDataStructure[d.key] = i
}
// Now dump all data per specific type using the array helper function.
for i: UInt32 in array(myDataStructure) {
print(i)
}
for i: Float in array(myDataStructure) {
print(i)
}
for i: Bool in array(myDataStructure) {
print(i)
}
}
func array<T>(_ data: [ObjectIdentifier: [Any]]) -> [T] {
return data[ObjectIdentifier(T.self)] as? [T] ?? []
}
To search for a string included in a struct I use:
let results = myArray.filter( {$0.model.localizedCaseInsensitiveContains("bu")} )
But say the struct has several properties that I'd like to search - or maybe I'd even like to search all of them at one time. I can only filter primitive types so leaving 'model' out won't work.
Solution -------------------------
While I really liked the idea of using key paths as Matt suggested below, I ended up adding a function to my struct that made my view controller code much cleaner:
struct QuoteItem {
var itemIdentifier: UUID
var quoteNumber: String
var customerName: String
var address1: String
func quoteItemContains(_ searchString: String) -> Bool {
if self.address1.localizedCaseInsensitiveContains(searchString) ||
self.customerName.localizedCaseInsensitiveContains(searchString) ||
self.quoteNumber.localizedCaseInsensitiveContains(searchString)
{
return true
}
return false
}
Then, in my controller, quotes is an array of QuoteItem that I can search by simply writing:
searchQuoteArray = quotes.filter({ $0.quoteItemContains(searchString) })
This sounds like a job for Swift key paths. Just supply the key paths for the String properties you want to search.
struct MyStruct {
let manny = "Hi"
let moe = "Hey"
let jack = "Howdy"
}
let paths = [\MyStruct.manny, \MyStruct.moe, \MyStruct.jack]
let s = MyStruct()
let target = "y"
let results = paths.map { s[keyPath:$0].localizedCaseInsensitiveContains(target) }
// [false, true, true]
I hope i understood you correct. I think with this piece of code you can achieve what you want:
struct ExampleStruct {
let firstSearchString: String
let secondSearchString: String
}
let exampleOne = ExampleStruct(firstSearchString: "Hello", secondSearchString: "Dude")
let exampleTwo = ExampleStruct(firstSearchString: "Bye", secondSearchString: "Boy")
let exampleArray = [exampleOne, exampleTwo]
let searchString = "Hello"
let filteredArray = exampleArray.filter { (example) -> Bool in
// check here the properties you want to check
if (example.firstSearchString.localizedCaseInsensitiveContains(searchString) || example.secondSearchString.localizedCaseInsensitiveContains(searchString)) {
return true
}
return false
}
for example in filteredArray {
print(example)
}
This prints the following in Playgrounds:
ExampleStruct(firstSearchString: "Hello", secondSearchString: "Dude")
Let me know if it helps.
I have an array of dictionaries with the following type of structure (which is already sorted) :
[
[
"id": 1,
"name": "ItemA",
"url": "http://url.com"
],
[
"id": 32,
"name": "ItemB",
"url": "http://url.com"
],
...
]
Declared as an array of dictionaries for AnyObject :
var arrayApps = [[String:AnyObject]]()
This array of dictionaries is generated using SwiftyJson :
[..]
if let resData = swiftyJsonVar["data"].arrayObject {
self.arrayItems = resData as! [[String:AnyObject]]
}
[..]
My Goal is to display those items in sections by using the sections headers but after trying to figure it out and looking for an answer, i'm unable to move on.
I've tried to groupe the dictionaries by letters to get a result like this:
[
"A":{[foo1],[foo2]},
"D":{[foo3],[foo5]},
"F":{[foo4],[foo6]}
...
]
But no luck, i've always ended up with errors because my array contains "Optionals".
In summary :
How can I generate Alphabetical section headers based on the name inside a TableView using an array of dictionaries not grouped like the one given above in Swift 3 ?
Thank you in advance !!
You can use the .sorted(by: ) method of Array to compare to elements of you array with each other.
This yields a sortedArray:
let sortedArray = arrayOfApps.sorted(by: {($0["name"] as! String) <= ($1["name"] as! String)})
This will crash if the itemName is not a String but I left it to you to handle any errors. For example changing it to:
$0["name"] as? String ?? ""
EDIT:
// Removed examples and added extension to create desired result
I found one of my old projects where I wrote such extension. Changed it a bit to suit your needs, tell me if it needs some change still:
extension Array {
func sectionTitlesForArray(withName name: (Element) -> String) -> Array<(title: String, elements: NSMutableArray)> {
var sectionTitles = Array<(title: String, elements: NSMutableArray)>()
self.forEach({ element in
var appended = false
sectionTitles.forEach({ title, elements in
if title == name(element) {
elements.add(element)
appended = true
}
})
if appended == false {
sectionTitles.append((title: name(element), elements: [element]))
}
})
return sectionTitles
}
}
// Usage single letter as Section title:
let sectionTitles = arrayOfApps.sectionTitlesForArray(withName: {
let name = $0["name"] as! String
return String(name[name.startIndex])
})
// Quick dirty pretty-print:
sectionTitles.forEach({ sectionTitle in
print("Section title: \(sectionTitle.title) \n")
sectionTitle.elements.forEach({ object in
let element = object as! Dictionary<String,Any>
print("Element name: \(element["name"]!)")
})
print("")
})
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