I need help with creating a custom body when sending POST request with Alamofire.
I'm sending to API products. There are two types of products. First type has just quantity, second one - different quantities(size_id) and quantities that match each size_id.
Final body should look like:
"factory_id": "1"
"order_products[0][product_id]": "1"
"order_products[0][size_id]": "2"
"order_products[0][quantity]": "10"
"order_products[1][product_id]": "1"
"order_products[1][size_id]": "3"
"order_products[1][quantity]": "10"
"order_products[1][product_id]": "2"
"order_products[1][size_id]": "2"
"order_products[1][quantity]": "10"
"order_products[2][product_id]": "3"
"order_products[2][quantity]": "10"
Here's what I achieved so far:
var createOrderBody = [String: Any]()
let productIds = ["1", "2", "3"]
var body = [String: Any]()
var quantity = ["1" : "10", "2": "10"]
var noIdQuantity = ["10"]
var finalBody = [String: Any]()
func formBody(products: String, factoryId: String, productId: String, size_id: String, quantity: String) -> [String: Any] {
createOrderBody["factory_id"] = factoryId
createOrderBody["order_products[\(products)][product_id]"] = productId
createOrderBody["order_products[\(products)][size_id]"] = size_id
createOrderBody["order_products[\(products)][quantity]"] = quantity
return createOrderBody
}
for (index, value) in productIds.enumerated() {
for (id, size) in quantity {
print(id)
print(size)
body = formBody(products: String(index), factoryId: "1", productId: String(value), size_id: id, quantity: size)
print("Body quantity - ", body)
}
}
And the result I have is:
"factory_id": "1",
"order_products[0][product_id]": "1"
"order_products[0][size_id]": "2",
"order_products[0][quantity]": "10",
"order_products[1][product_id]": "2",
"order_products[1][size_id]": "2",
"order_products[1][quantity]": "10",
"order_products[2][product_id]": "3",
"order_products[2][size_id]": "2",
"order_products[2][quantity]": "10",
As you can see, I have almost achieved desired result, but the problem is that it is adding only last element of quantity dictionary and omits other values. Also, I don't know how to add quantity to the product, that doesn't have size_id
Also, I know that it is not a good practice to place for in loop inside other for in loop but I'm new to development and this is the best idea that I have came up with.
Would be grateful for any help with this issue, as I've been battling with it almost for a week right now.
Many thanks and have a nice weekends!
After studying and searching on the Internet, here's a solution to the issue we have here. For reference, original post here - Original Post
Assuming that every product has to hold it's own quantity:
We can define a struct like this:
struct Product {
let id: String
let quantities: [(sizeId: String, quantity: Int)]?
let noIdQuantity: Int?
init(id: String, quantities: [(sizeId: String, quantity: Int)]) {
self.id = id
self.quantities = quantities
self.noIdQuantity = nil
}
init(id: String, quantity: Int) {
self.id = id
self.quantities = nil
self.noIdQuantity = quantity
}
}
With the struct above, we just need only one input variable and one output variable:
// Define input `product with id` as an Array of `Product`
let products = [
Product(id: "1", quantities: [("1", 10), ("2", 10)]),
Product(id: "2", quantities: [("1", 10), ("2", 10)]),
Product(id: "3", quantities: [("1", 15), ("2", 20)]),
Product(id: "4", quantity: 10),
]
// Output dictionary
var body = [String: Any]()
To make entries for a single Product into a Dictionary:
extension Product {
func formBody(_ index: inout Int, into body: inout [String: Any]) {
if let quantities = self.quantities {
for (sizeId, quantity) in quantities {
body["order_products[\(index)][product_id]"] = self.id
body["order_products[\(index)][size_id]"] = sizeId
body["order_products[\(index)][quantity]"] = quantity
index += 1
}
}
if let quantity = self.noIdQuantity {
body["order_products[\(index)][product_id]"] = self.id
body["order_products[\(index)][quantity]"] = quantity
index += 1
}
}
}
And use them as follows:
var index = 0
body["factory_id"] = "1"
for product in products {
product.formBody(&index, into: &body)
}
print("Body quantity - ", body)
body.sorted {$0.key < $1.key}.forEach{print("\($0.key)=\($0.value)")} //For showing readable result, not for Alammofire body
So, the final result would be:
factory_id=1
order_products[0][product_id]=1
order_products[0][quantity]=10
order_products[0][size_id]=1
order_products[1][product_id]=1
order_products[1][quantity]=10
order_products[1][size_id]=2
order_products[2][product_id]=2
order_products[2][quantity]=10
order_products[2][size_id]=1
order_products[3][product_id]=2
order_products[3][quantity]=10
order_products[3][size_id]=2
order_products[4][product_id]=3
order_products[4][quantity]=15
order_products[4][size_id]=1
order_products[5][product_id]=3
order_products[5][quantity]=20
order_products[5][size_id]=2
order_products[6][product_id]=4
order_products[6][quantity]=10
Hope this helps someone, who has same complex structure or similar.
Related
I am looking to find an efficient way to extract both unique and duplicate elements from array with identifiable elements in Swift.
Just to make clear there are two points here:
I need to know both unique and duplicate elements, and just removing the duplicates is not what I need.
I want to group the duplicate elements by their id
I made the following extension on Array:
public extension Array where Element: Identifiable {
/// A helper to filter duplicate items in an array
/// - Returns uniquElements: Unique elements in array, considering the first element to be chosen when there are duplicates
/// - Returns duplicateElementGroups: Duplicate elements in the array grouped by their id
func filterDuplicates() -> (uniquElements: [Element], duplicateElementGroups: [Element.ID: [Element]]) {
var uniqueElements = [Element]()
var uniqueIDs = [Element.ID: Element]()
var duplicateElements = [Element.ID: [Element]]()
for element in self {
if let existingDuplicateElement = uniqueIDs[element.id] {
if let existingDuplicatesArray = duplicateElements[element.id] {
duplicateElements[element.id] = existingDuplicatesArray + [element]
} else {
duplicateElements[element.id] = [existingDuplicateElement, element]
}
} else {
uniqueIDs[element.id] = element
uniqueElements.append(element)
}
}
return (uniqueElements, duplicateElements)
}
}
Another solution can be the following, where we store less storage, but we iterate one more time. It is also a bit more readable:
func filterDuplicates() -> (uniquElements: [Element], duplicateElementGroups: [Element.ID: [Element]]) {
var uniqueElements = [Element]()
var duplicateElements = [Element.ID: [Element]]()
let mapppedDictionary = Dictionary(grouping: self, by: \.id)
for element in self {
if duplicateElements[element.id] == nil {
uniqueElements.append(element)
}
if let allValuesForKey = mapppedDictionary[element.id], allValuesForKey.count > 1 {
duplicateElements[element.id] = allValuesForKey
}
}
return (uniqueElements, duplicateElements)
}
I also wrote the test to verify it works:
// MARK: - Dummy Data
struct SampleIdentifiableElement: Identifiable {
let id: String
let title: String
}
func test_whenFilterDuplicatesInArray_uniqueAndDuplicateItemsReturns() {
// Given
let sampleElements: [SampleIdentifiableElement] = [
.init(id: "1", title: "title 1"),
.init(id: "2", title: "title 2 - first duplicate"),
.init(id: "2", title: "title 2 - second duplicate"),
.init(id: "2", title: "title 2 - third duplicate"),
.init(id: "3", title: "title 3"),
.init(id: "4", title: "title 4 - first duplicate"),
.init(id: "4", title: "title 4 - second duplicate")
]
// When
let (uniquElements, duplicateElementGroups) = sampleElements.filterDuplicates()
// Then
XCTAssertEqual(uniquElements.count, 4)
XCTAssertEqual(duplicateElementGroups.count, 2)
XCTAssertEqual(duplicateElementGroups["2"]?.count, 3)
XCTAssertEqual(duplicateElementGroups["4"]?.count, 2)
XCTAssertEqual(duplicateElementGroups["2"]?.first?.title, "title 2 - first duplicate")
XCTAssertEqual(duplicateElementGroups["2"]?.last?.title, "title 2 - third duplicate")
XCTAssertEqual(uniquElements.first?.title, "title 1")
XCTAssertEqual(uniquElements.last?.title, "title 4 - first duplicate")
}
I wonder how to make more efficient way in terms of time and space complexity?
Currently both have the time complexity of O(n) and space complexity of O(n). However, in the first solution we iterate once through all items, while we store more data. The second one takes less space, but we iterate one more time.
How can I count the teamMember in team A?
class BasketBallTeam {
var teamName: String
var teamMember: [String]
init(teamName: String, teamMember: [String]) {
self.teamName = teamName
self.teamMember = teamMember
}
}
let highSchoolTeam: [BasketBallTeam] = [
BasketBallTeam(teamName: "A", teamMember: [
"John", "Sam", "Tom", "Ricky", "George"
]),
BasketBallTeam(teamName: "B", teamMember: [
"Jason", "Michael", "Daniel", "Henry", "David", "Luke"
]),
]
Just count it.
As highSchoolTeam is an array you have to get the first item
let numberOfMembers = highSchoolTeam.first?.teamMember.count
Please name teamMember in plural form teamMembers (or even members) to indicate the array
If you're doing this a lot, it would be best to create a dictionary that lets you easily access teams by their names:
struct BasketBallTeam {
var name: String
var members: [String]
init(name: String, members: [String]) {
self.name = name
self.members = members
}
}
let highSchoolTeams: [BasketBallTeam] = [
BasketBallTeam(name: "A", members: [
"John", "Sam", "Tom", "Ricky", "George"
]),
BasketBallTeam(name: "B", members: [
"Jason", "Michael", "Daniel", "Henry", "David", "Luke"
]),
]
let teamsByName = Dictionary(uniqueKeysWithValues:
highSchoolTeams.lazy.map { team in (key: team.name, value: team) })
Which makes looking up any team by name fast and easy:
let teamA = teamsByName["A"]
let teamASize = teamA?.members.count
print(teamASize as Any)
If you only need to do this once, you can use first(where:):
let teamA = highSchoolTeams.first(where: { $0.name == "A"})
You can get the count for each team as follows:
for team in highSchoolTeam {
print(team.teamMember.count)
}
You can simply use,
first(_:) on highSchoolTeam to get the team where teamName == "A".
count on teamMember to get the total count of teamMembers from the BasketBallTeam object obtained in step-1.
It goes like,
let teamAMemberCount = highSchoolTeam.first{ $0.teamName == "A" }?.teamMember.count
print(teamAMemberCount) //Output: 5
I made up a method to add a variable rankto an array of structs.
The array friutsArrayis created like in the function makeFriuts(). After that, the data gets sorted and according to this, every item gets a rank, respectively index.
In the end I need the FriutsWithRankstruct like it is.
But I´m wondering if there is a better, more effective way to to that. Maybe by even skipping the whole Friuts struct:
struct Friuts {
var name: String
var price: Double
}
struct FriutsWithRank {
var name: String
var price: Double
var rank: Int
}
var friutsArray = [Friuts]()
func makeFriuts() {
friutsArray.append(Friuts(name: "mango", price: 1.2))
friutsArray.append(Friuts(name: "banana", price: 0.79))
friutsArray.append(Friuts(name: "orange", price: 2.2))
}
func makeFriutsWithRank(data: [Friuts]) -> [FriutsWithRank] {
let dataSorted = data.sorted { $1.price < $0.price }
var datatoappend = [FriutsWithRank]()
var i = 0
dataSorted.forEach { fruit in
i += 1
let name = fruit.name
let price = fruit.price
let rank = i
let result = FriutsWithRank(name: name, price: price, rank: rank)
datatoappend.append(result)
}
return datatoappend
}
let friutsArrayWithRank = makeFriutsWithRank(data: friutsArray)
With more effective i mean not necessarily less code. I think the two arrays are now created with two iterations. Is it possible to skip the whole Fruits struct and work just with one struct and one iteration?.
I have applied some modification on your code, please read the inline comments. Not much more optimised, but more readable for sure.
// Typo fixed + it is a single Fruit, not Fruits
struct Fruit {
var name: String
var price: Double
}
struct FruitsWithRank {
// You already have a variable holding name and price, Friut
// Lets just reuse Fruit object
var fruit: Fruits
var rank: Int
}
var fruits = [Fruit]()
func makeFruits() {
fruits.append(Fruit(name: "mango", price: 1.2))
fruits.append(Fruit(name: "banana", price: 0.79))
fruits.append(Fruit(name: "orange", price: 2.2))
}
func makeFruitsWithRank(data: [Fruits]) -> [FruitsWithRank] {
let dataSorted = data.sorted { $1.price < $0.price }
var datatoappend = [FruitsWithRank]()
// Use `enumerated` to get index and the object straight away
for (index, fruit) in dataSorted.enumerated() {
// Just init your `FruitsWithRank` with the fruit and the index
let rankedFruit = FruitsWithRank(fruit: fruit, rank: index)
// Add to your array
datatoappend.append(rankedFruit)
}
return datatoappend
}
let fruitsWithRank = makeFruitsWithRank(data: fruitsArray)
EDIT:
Following the edits of your question, i have applied some changes. If you need FruitsWithRank having name and price, you can just create tuples with name and price, and create an array straight away without any loops or appends. You can omit the makeFruitsWithRank function, and sort, enumerate and create your types straight on the tuple array.
struct FruitsWithRank {
var rank: Int
var name: String
var price: Double
}
let rankedFruits: [FruitsWithRank] = [
(name: "mango", price: 1.2),
(name: "banana", price: 0.79),
(name: "orange", price: 2.2)]
.sorted { $0.price < $1.price }
.enumerated()
.map({ return FruitsWithRank(rank: $0 + 1, name: $1.name, price: $1.price) })
In the end this isn't much more efficient than your code, but it is more compact:
func makeFriutsWithRank(data: [Friuts]) -> [FriutsWithRank] {
let dataMapped = data.sorted { $1.price < $0.price }
.enumerated()
.map { FriutsWithRank(name: $1.name, price: $1.price, rank: $0 + 1) }
return dataMapped
}
Is it really necassary to have Struct? Because you already have sorted, the index can serve as rank.
I have a large array and need to access it by a key (a lookup) so I need to create Dictionary. Is there a built in function in Swift 3.0 to do so, or do I need to write it myself?
First I will need it for a class with key "String" and later on maybe I will be able to write a template version for general purpose (all types of data and key).
Note for 2019. This is now simply built-in to Swift 5, uniqueKeysWithValues and similar calls.
Is that it (in Swift 4)?
let dict = Dictionary(uniqueKeysWithValues: array.map{ ($0.key, $0) })
Note:
As mentioned in the comment, using uniqueKeysWithValues would give a fatal error (Fatal error: Duplicate values for key: 'your_key':) if you have duplicated keys.
If you fear that may be your case, then you can use init(_:uniquingKeysWith:) e.g.
let pairsWithDuplicateKeys = [("a", 1), ("b", 2), ("a", 3), ("b", 4)] // or `let pairsWithDuplicateKeys = array.map{ ($0.key, $0) }`
let firstValues = Dictionary(pairsWithDuplicateKeys, uniquingKeysWith: { (first, _) in first })
print(firstValues)
//prints ["a": 1, "b": 2]
let lastValues = Dictionary(pairsWithDuplicateKeys, uniquingKeysWith: { (_, last) in last })
print(lastValues)
//prints ["a": 3, "b": 4]
On Swift 4, you can achieve this by using Dictionary's grouping:by: initializer
For ex:
You have class named A
class A {
var name: String
init(name: String) {
self.name = name
}
// .
// .
// .
// other declations and implementions
}
Next, you have an array of objects of type A
let a1 = A(name: "Joy")
let a2 = A(name: "Ben")
let a3 = A(name: "Boy")
let a4 = A(name: "Toy")
let a5 = A(name: "Tim")
let array = [a1, a2, a3, a4, a5]
Let's say you want to create a Dictionary by grouping all the names by their first letter. You use Swifts Dictionary(grouping:by:) to achieve this
let dictionary = Dictionary(grouping: array, by: { $0.name.first! })
// this will give you a dictionary
// ["J": [a1], "B": [a2, a3], "T": [a4, a5]]
source
Note however that the resulting Dictionary "dictionary" is of type
[String : [A]]
it is not of type
[String : A]
as you may expect. (Use #uniqueKeysWithValues to achieve the latter.)
I think you're looking for something like this:
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
}
}
You can now do:
struct Person {
var name: String
var surname: String
var identifier: String
}
let arr = [Person(name: "John", surname: "Doe", identifier: "JOD"),
Person(name: "Jane", surname: "Doe", identifier: "JAD")]
let dict = arr.toDictionary { $0.identifier }
print(dict) // Result: ["JAD": Person(name: "Jane", surname: "Doe", identifier: "JAD"), "JOD": Person(name: "John", surname: "Doe", identifier: "JOD")]
If you'd like your code to be more general, you could even add this extension on Sequence instead of Array:
extension Sequence {
public func toDictionary<Key: Hashable>(with selectKey: (Iterator.Element) -> Key) -> [Key:Iterator.Element] {
var dict: [Key:Iterator.Element] = [:]
for element in self {
dict[selectKey(element)] = element
}
return dict
}
}
Do note, that this causes the Sequence to be iterated over and could have side effects in some cases.
As others already said, we need to understand which are the keys.
However I am trying to provide a solution to my interpretation of your question.
struct User {
let id: String
let firstName: String
let lastName: String
}
Here I am assuming that 2 users with the same id cannot exist
let users: [User] = ...
let dict = users.reduce([String:User]()) { (result, user) -> [String:User] in
var result = result
result[user.id] = user
return result
}
Now dict is a dictionary where the key is the user id and the value is the user value.
To access a user via its id you can now simply write
let user = dict["123"]
Update #1: General approach
Given an array of a given type Element, and a closure that determine the key of an Element, the following generic function will generate a Dictionary of type [Key:Element]
func createIndex<Key, Element>(elms:[Element], extractKey:(Element) -> Key) -> [Key:Element] where Key : Hashable {
return elms.reduce([Key:Element]()) { (dict, elm) -> [Key:Element] in
var dict = dict
dict[extractKey(elm)] = elm
return dict
}
}
Example
let users: [User] = [
User(id: "a0", firstName: "a1", lastName: "a2"),
User(id: "b0", firstName: "b1", lastName: "b2"),
User(id: "c0", firstName: "c1", lastName: "c2")
]
let dict = createIndex(elms: users) { $0.id }
// ["b0": {id "b0", firstName "b1", lastName "b2"}, "c0": {id "c0", firstName "c1", lastName "c2"}, "a0": {id "a0", firstName "a1", lastName "a2"}]
Update #2
As noted by Martin R the reduce will create a new dictionary for each iteration of the related closure. This could lead to huge memory consumption.
Here's another version of the createIndex function where the space requirement is O(n) where n is the length of elms.
func createIndex<Key, Element>(elms:[Element], extractKey:(Element) -> Key) -> [Key:Element] where Key : Hashable {
var dict = [Key:Element]()
for elm in elms {
dict[extractKey(elm)] = elm
}
return dict
}
let pills = ["12", "34", "45", "67"]
let kk = Dictionary(uniqueKeysWithValues: pills.map{ ($0, "number") })
["12": "number", "67": "number", "34": "number", "45": "number"]
swift5 swift4
The following converts an array to a dictionary.
let firstArray = [2,3,4,5,5]
let dict = Dictionary(firstArray.map { ($0, 1) } , uniquingKeysWith: +)
Swift 5
extension Array {
func toDictionary() -> [Int: Element] {
self.enumerated().reduce(into: [Int: Element]()) { $0[$1.offset] = $1.element }
}
}
This extension works for all sequences (including arrays) and lets you select both key and value:
extension Sequence {
public func toDictionary<K: Hashable, V>(_ selector: (Iterator.Element) throws -> (K, V)?) rethrows -> [K: V] {
var dict = [K: V]()
for element in self {
if let (key, value) = try selector(element) {
dict[key] = value
}
}
return dict
}
}
Example:
let nameLookup = persons.toDictionary{($0.name, $0)}
Just do it simply,
let items = URLComponents(string: "https://im.qq.com?q=13&id=23")!.queryItems!
var dic = [String: Any?]()
items.foreach {
dic[$0.name] = $0.value
}
reduce is not very suitable,
let dic: [String: Any?] = items.reduce([:]) { (result: [String: Any?], item: URLQueryItem) -> [String: Any?] in
var r = result
r[item.name] = item.value // will create an copy of result!!!!!!
return r
}
As i understand from you're question you would like to convert to Array to Dictionary.
In my case i create extension for the Array and keys for the dictionary will be indexes of the Array.
Example:
var intArray = [2, 3, 5, 3, 2, 1]
extension Array where Element: Any {
var toDictionary: [Int:Element] {
var dictionary: [Int:Element] = [:]
for (index, element) in enumerate() {
dictionary[index] = element
}
return dictionary
}
}
let dic = intArray.toDictionary
Compatible with Swift 5 Standard Library (Xcode 10.2+ , iOS 12.2).
Here's an example of usage of an initializer init(uniqueKeysWithValues:)
The input let array: [String] = Locale.isoRegionCodes is an array of ISO31661-2 codes represented by a string.
let countryCodeAndName: [String: String] = Dictionary(uniqueKeysWithValues: Locale.isoRegionCodes.map { ($0, Locale.current.localizedString(forRegionCode: $0) ?? "")} )
Returned dictionary, will list all regions with ISO31661-2 code as a key and a localized region name as a value.
Output:
...
"PL":"Poland"
"DE":"Germany"
"FR":"France"
"ES":"Spain"
...
Example 2:
let dictionary: [String: String] = Dictionary(uniqueKeysWithValues: [ ("key1", "value1"), ("key2", "value2")] )
Output:
["key1": "value1", "key2": "value2"]
Important:
Precondition: The sequence must not have duplicate keys.
Code below will crash an app:
let digitWords = ["one", "two", "three", "four", "five", "five"]
let wordToValue = Dictionary(uniqueKeysWithValues: zip(digitWords, 1...6))
with:
Fatal error: Duplicate values for key: 'five'
If you want to follow the pattern set out by map and reduce in swift you could do something nice and functional like this:
extension Array {
func keyBy<Key: Hashable>(_ keyFor: (Element) -> Key) -> [Key: Element] {
var ret = [Key: Element]()
for item in self{
ret[keyFor(item)] = item
}
return ret
}
}
Usage:
struct Dog {
let id: Int
}
let dogs = [Dog(id: 1), Dog(id: 2), Dog(id: 3), Dog(id: 4)]
let dogsById = dogs.keyBy({ $0.id })
// [4: Dog(id: 4), 1: Dog(id: 1), 3: Dog(id: 3), 2: Dog(id: 2)]
Swift way:
extension Sequence {
func toDictionary<Key: Hashable>(with selectKey: (Element) -> Key) -> [Key: Element] {
reduce(into: [:]) { $0[selectKey($1)] = $1 }
}
}
// let arr = [Person(id: 1, name: "Alan")]
// arr.toDictionary { $0.id }
// ==
// [1: Person(id: 1, name: "Alan")]
I am new at iOS development. I am trying to do the following:
Store a title, that allows any number of subcategories to be added below it. Each subcategory needs to have 2 integers attached to it. I need to be able to use, edit, remove, and add new titles, subcategories, and integers.
"Title":
"Subcategory": int, int
"Subcategory": int, int
"Title":
"Subcategory": int, int
"Subcategory": int, int
"Subcategory": int, int
"Subcategory": int, int
I have tried several times with structs and arrays.
For example:
struct everyThing {
var titles = ["Title 1", "Title 2", "Title 3"]
var subcategories = ["subcat1", "subcat2", "subcat3", "subcat4", "subcat5"]
var integers: [Double] = [5.0,10.0,7.0,15,3,6,7,8,12,14,13,15]
}
struct grouping1{
var title = everyThing().titles[0]
var subcategories = everyThing().subcategories[0..<2]
var integers = everyThing().workRest[0..<2]
}
struct grouping2{
var title = everyThing().titles[1]
var subcategories = everyThing().integers[2..<4]
var integers = everyThing().integers[2..<4]
}
It becomes impossible to keep track of and scale, as any number of subcategories can be added under a particular title.
Any ideas of the best way to organize this data? Let me know if this is too vague.
You can use a dictionary [String : [String : (Int, Int)]]
let dictionary: [String : [String : (Int, Int)]] = [
"Title1" : [
"subcat1" : (5, 10),
"subcat2" : (7, 15)
],
"Title2" : [
"subcat3" : (3, 6),
"subcat4" : (7, 8),
"subcat5" : (12, 14)
]
]
To get the tuple of integers (Int, Int) under a category and subcategory, you can use
let tuple: (Int, Int) = dictionary[title]![subcategory]!
But, this uses forced unwrapping using the !. Instead, a safer way to do it that won't cause your app to crash would be
let tuple: (Int, Int)? = dictionary[title]?[subcategory]
Then, to get the values in the tuple you could use
let val1: Int? = tuple?.0
let val2: Int? = tuple?.1
To just set the value 0 instead of nil when the value does not exist, you could use the ?? operator
let val1: Int = tuple?.0 ?? 0
let val2: Int = tuple?.1 ?? 0
If you wanted to loop through all the values, it could be done with
for title in dictionary.keys{
for subcategory in dictionary[title]!.keys{
//we can force unwrapping because we are sure the
//value will not be nil, because we are looping
//through the keys of the dictionary
let value1: Int = dictionary[title]![subcategory]!.0
let value2: Int = dictionary[title]![subcategory]!.1
//use title, subcategory, value1, and value2 as you please
}
}
Setting a value is as simple as
dictionary["newOrExistingTitle"]["newOrExistingSubcategory"] = (num1, num2)
For example
dictionary["Title1"]["subcat2"] = (8, 2)
You need 2 model values
struct SubCategory {
let title: String
let value0: Int
let value1: Int
}
struct Category {
var title: String
private (set) var subcategories = [SubCategory]()
init(title:String) {
self.title = title
}
}
Let's see what you can do now.
Adding a 2 categories
var categories = [Category]()
let category0 = Category(title: "Category 0")
categories.append(category0)
let category1 = Category(title: "Category 1")
categories.append(category1)
// [Category(title: "Category 0", subcategories: []), Category(title: "Category 1", subcategories: [])]
Adding a Subcategories
var cat = categories[0]
cat.subcategories.append(SubCategory(title: "Sub0", value0: 1, value1: 2))
cat.subcategories.append(SubCategory(title: "Sub1", value0: 3, value1: 4))
categories[0] = cat
// [Category(title: "Category 0", subcategories: [SubCategory(title: "Sub0", value0: 1, value1: 2), SubCategory(title: "Sub1", value0: 3, value1: 4)]), Category(title: "Category 1", subcategories: [])]
Changing the Category Title
var cat = categories[0]
cat.title = "New title"
categories[0] = cat
// [Category(title: "New title", subcategories: []), Category(title: "Category 1", subcategories: [])]