How can I collapse a struct array? - swift

I have a struct for my items:
struct item: Decodable {
var category_id: Int
var name: String
}
Which I'm receiving through JSONDecoder().decode into an array (items: [item]).
And I'd like to collapse it into a structure like this:
struct collapsed{
var category_id = Int()
var items = [String]()
}
So that I can loop through it more conveniently. How can I do that?
So I have this variable:
items: [item]
Which looks like this:
items
[0]
-category_id: 0
-name: "item1"
[1]
-category_id: 1
-name: "item2"
[2]
-category_id: 0
-name: "item3"
[3]
-category_id: 1
-name: "item4"
[4]
-category_id: 0
-name: "item5"
And I want to collapse it to this:
collapseditems: [collapsed]
Which should look something like this:
collapseditems
[0]
-category_id: 0
-items:
[0]: "item1"
[1]: "item3"
[2]: "item5"
[1]
-category_id: 1
-items:
[0]: "item2"
[1]: "item4"

First thing - struct names should start with uppercase letters.
There are a few ways you can translate your array of Item to an array of Collapsed. Here is one approach that first groups the array of Item into a dictionary keyed by category_id. Then that dictionary is mapped into an array of Collapsed. Finally that array is sorted by category_id.
struct Item: Decodable {
var category_id: Int
var name: String
}
struct Collapsed {
let category_id: Int
var items: [String]
}
// Your array of Item - here is some test data
let items: [Item] = [
Item(category_id: 0, name: "item1"),
Item(category_id: 1, name: "item2"),
Item(category_id: 0, name: "item3"),
Item(category_id: 1, name: "item4"),
Item(category_id: 0, name: "item5")
]
// Create a dictionary where the keys are category_id and the values are an array of Item with the same category_id
let mapping = Dictionary(grouping: items, by: { $0.category_id })
// Map that dictionary into an array of Sorted and then sort that array by category_id
let collapsedItems: [Collapsed] = mapping
.map { Collapsed(category_id: $0.key, items: $0.value.map { $0.name }) }
.sorted { $0.category_id < $1.category_id }

You can achieve this by using below code.
var sortedList: [sorted] = []
for i in item {
if let index = sortedList.index(where: { $0.category_id == i.category_id }) {
sortedList[index].items.append(i.name)
} else {
sortedList.append(sorted(category_id: i.category_id, items: [i.name]))
}
}
print(sortedList)

For first question, do you want to make a sorted list of item struct?
If yes, you can use sorted method of Array like this.
let sorted = items.sorted { $0.category_id < $1.category_id }
For Second question, first struct has default value for its properties, but second one hasn't. Second one, therefore, does NOT offer an empty constructor.

Related

Filter a nested arrays of structs and return the child object in swift

I have read multiple discussions about filtering nested arrays in swift, however they all return the parent object.
Let's have these simple data:
struct MenuSection: Identifiable {
let id: id
var name: String
var menuRows: [MenuRow]
}
struct MenuRow: Identifiable, Hashable {
let id: id
var name: String
}
private let menuSections: [MenuSection] = [
MenuSection(id: 0, name: "Menu Group 1", menuRows: [
MenuRow(id: 0, name: "Menu 1")
]),
MenuSection(id: 1, name: "Menu Group 2", menuRows: [
MenuRow(id: 1, name: "Menu 2"),
MenuRow(id: 2, name: "Menu 3")
])
]
My goal is to get the MenuRow with its id.
So, I create this function and it works:
func getMenuRowWithId(menuRowId: id) -> MenuRow? {
for menuSection in menuSections {
for menuRow in menuSection.menuRows {
if menuRow.id == menuRowId {
return menuRow
}
}
}
return nil
}
However, I want to do something more swifty (and maybe more efficient).
I tried something like:
var filtered = menuSections.filter { $0.menuRows.filter { $0.id == 1 }.count != 0 }
but it's returning the MenuSection containing the right MenuRow.
=> I only want the menuRow.
I have tried many things, such playing with compactMap/flatMap to flatten the array before filtering. No way.
How can I browse the menuSections and obtain ONLY the right menuRow ?
Thanks
You can use flatMap first to get all MenuRow objects in one array and then find the correct object in that array using first(where:)
let menuRow = menuSections.flatMap(\.menuRows).first(where: { $0.id == 1 })

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"]]

Looping through a multidimensional array in swift

So I am trying to iterate over an NSArray. My NSArray is an array of an array of strings. Here is a copy-paste of the first 1.5 elements
(
(
"Tater Tot Nachos",
"Fried Feta",
"The Ultimate Feta Bread",
"Cheese Bread",
"Aubrees Bread",
"The Wings!",
"Coconut Grove Chicken Sicks",
"Far East Wings",
"Bacon Brussels Sprouts"
),
(
"Shaved Brussels Sprout Salad",
"Greek Salad",
"Coronado Cobb Salad",
"Harvest Salad",
This is the function that's giving me the headache
func createMenu() {
if let list = cellDescripters {
for(index, item) in list.enumerated() {
for food in item {
//DO SOMETHING WITH "FOOD"
}
}
}
}
' cellDescripters ' Is a global variable and it is the array I was outlining at the top, basically an array of arrays of strings.
When I print the type of ' item ' I see it's of type __NSArrayM which is an NSMutableArray from my understanding. Looking at documentation NSMutableArrays are iterable.
However when I go to compile this code I get the error:
Type 'Any' does not conform to protocol 'Sequence'
Any help would be greatly appreciated.
I think following example give you help
for example i have array of string array like you =
[["beverages", "food", "suppliers"],["other stuff", "medicine"]];
var arrayExample = [["beverages", "food", "suppliers"],["other stuff", "medicine"]];
//Iterate array through for loop
for(index, item) in arrayExample.enumerated()
{
for food in item
{
print("index : \(index) item: \(food)")
}
}
OUTPUT
index : 0 item: beverages
index : 0 item: food
index : 0 item: suppliers
index : 1 item: other stuff
index : 1 item: medicine
Here's a more generic solution for iterating 2D arrays in Swift. Tested in Swift 4 on iOS 13.
Works only on Swift arrays, see the following link for converting your NSArray to Arrays: https://stackoverflow.com/a/40646875/1960938
// 2D array extension explanation: https://stackoverflow.com/a/44201792/1960938
fileprivate extension Array where Element : Collection, Element.Index == Int {
typealias InnerCollection = Element
typealias InnerElement = InnerCollection.Iterator.Element
func matrixIterator() -> AnyIterator<InnerElement> {
var outerIndex = self.startIndex
var innerIndex: Int?
return AnyIterator({
guard !self.isEmpty else { return nil }
var innerArray = self[outerIndex]
if !innerArray.isEmpty && innerIndex == nil {
innerIndex = innerArray.startIndex
}
// This loop makes sure to skip empty internal arrays
while innerArray.isEmpty || (innerIndex != nil && innerIndex! == innerArray.endIndex) {
outerIndex = self.index(after: outerIndex)
if outerIndex == self.endIndex { return nil }
innerArray = self[outerIndex]
innerIndex = innerArray.startIndex
}
let result = self[outerIndex][innerIndex!]
innerIndex = innerArray.index(after: innerIndex!)
return result
})
}
}
An example usage:
let sampleMatrix = [
["a", "b", "c"],
["d", "e"],
[],
["f"],
[]
]
// Should print: a, b, c, d, e, f
for element in sampleMatrix.matrixIterator() {
print(element)
}

Generate section headers from dictionary [Swift 3]

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("")
})

Delete array of structs from an array of structs Swift

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