Swift - best way to handle a dictionary of dictionaries? - arrays

I have an array of people that all have a list of favourite books.
Each book has several properties, such as book title, book description, book author.
What I want to do is create a dictionary of dictionaries to be able to access a specific person, and then access each book of that person and extract the book name, book title author etc from that that.
currently what I am doing is the declaring a dictionary like so:
var dictToHoldBooKValue = [String : [String : AnyObject]]()
the first String is the key, which I will create as the name of the person, the second pard is a dictionary, which will have a key of type string (which I will set as the book property name like "book Title" or "book author" and the any object is the value of that key.
example: I modify the dictionary like so:
dictToHoldBooKValue["Mark"] = ["book Title" : (bookTitle as? String)]
However, this approach does not seem to be working because "Mark" has several books he likes and so when I print the dict count it only shows one. Also, I have no idea how I would use this dict to to get at the book author property or any other property.
Anybody know a more elegant solution?
Thanks!

Make a struct to represent a book. Make it contain all the book attributes you want to track.
struct Book {
var title: String
var author: String
var year: Int
}
Your outer dictionary will be exactly as you described.
Your inner dictionary will have its key as the name of the book and it will contain Book structs.
var dictToHoldBooKValue = [String : [String : Book]]()
dictToHoldBooKValue["Mark"] = [
"You Only Live Twice" : Book("You Only Live Twice", "Ian Flemming", 1961),
"Casino Royale" : Book("Casino Royale", "Ian Flemming", 1959)
]
Access it like this:
print(dictToHoldBooKValue["Mark"]["You Only Live Twice"].author)
This is all fine if you are loading this data from somewhere and want to show it in a specific way. If you need to query data in a more flexible way, you should consider using CoreData.

If you want to use key-value coding, you can use typealiases to simplify your code:
typealias Book = [String: AnyObject]
typealias Books = [Book]
typealias Person = [String: AnyObject]
var people = [Person]()
var mark = Person()
var marksBooks = Books()
var theLittlePrince = [
"title" : "The Little Prince",
"author": "Antoine de Saint-Exupéry"
]
marksBooks.append(theLittlePrince)
mark["books"] = marksBooks
people.append(mark)
print(people)
Prints:
[["books": (
{
author = "Antoine de Saint-Exup\U00e9ry";
title = "The Little Prince";
}
)]]

Related

Swift : multi-dimensional structure organization and init

There are two simple problems here in initializing this structure.
One is the enunumerated value TS (I get error : Cannot convert value of type 'TournNames' to expected argument type 'TournamentName')
the other is initializing an array of strings (I get the error : Cannot convert value of type '[String]' to expected argument type 'TouramentScores'
Suppose I am trying to set up a structure to model the scores of tennis players and all of their matches in each of the major tournaments (just for fun). Each tournament has a name (e.g. Wimbledon) and a series of scores for that player (for example, in the opening match, their score might be "4-6, 6-4, 7-6, 6-2")... upto 7 matches in each tournament. Each player should have an array of four tournaments (names and array of scores), and eventually there should be an array of players. I am also trying to use enums not too successfully. Ideally, if I want to find how Roger Federer did in his third match of wimbledon this year, I would access something like player.tournament.wim.Roundof32 or something roughly like that. But before I can even get to playing with that, I can't seem to init dummy data for even a single tournament.
Any ideas? I don't think this is that hard of question but I just don't know each. See "*** this line" below for two lines that are problematic
// tournament name enum
enum TournNames : String {
case wim = "Wimbledom"
case fo = "French Open"
case ao = "Australian Open"
case uo = "US Open"
}
//
struct TournamentName {
var Tname : TournNames // = .wim
}
// This is the structure for a tournament score array with some dummy values.
struct TouramentScores {
var Match1 : String = "7-6, 6-4, 3-6, 7-6"
var Match2 : String = "7-6, 6-4, 3-6, 7-6"
}
// This is one entire Tournament record for one player = tournament name + array of scores ... the next goal but not used here until I get over these hurdles
struct TournamentResult {
var TournamentName : TournNames = .wim
var Scores : TouramentScores
}
// ... finally the structure of a player ...
struct DummyTennisPlayer {
var LastName : String // last name
var FirstName : String //first name
var TN : TournamentName
var TS : TouramentScores
// var WimRes : TournamentResult // to start a single tournament
// var SeasonResults : [TournamentResult] // ultimately should be an array of 4 tournaments
}
// trying to initialize some dummy data without success after trying several things
extension DummyTennisPlayer {
static var dummyResults : [DummyTennisPlayer] {
[
DummyTennisPlayer.init(
LastName : "Federer",
FirstName: "Roger",
TN : TournNames.wim // **** this line
,
TS : ["XX", "yy"] /// *** this line
)
]
}
}
As I think you're discovering, a simple series of nested types is unlikely to cut it here. As soon as you get to entities like players, tournaments, matches and lookups like "how Roger Federer did in his third match of wimbledon this year", you've become a candidate for using a database where you can manipulate one-to-many and many-to-many relationships. I can't tell you what database to use, and anyway that's a matter of opinion; from what you've said so far, SQLite would be sufficient (and I am personally not a fan of Core Data just for this kind of thing).
I guess your code is a kind of exercise, so before you go on later to Core Data or SQLite,
extension DummyTennisPlayer {
static var dummyResults: [DummyTennisPlayer] = [
DummyTennisPlayer(LastName: "Federer", FirstName: "Roger", WimbledomResult: TournamentResult(Scores: TouramentScores()))
]
}
should answer your question.
1 - To initialize a Swift struct, use the following syntax:
MyStruct(property1: "my property1 value", property2: "my property2 value")
2 - the tournament name property in TournamentResult is already set to .wim so you just need to initialize the Scores. As your TournamentScores properties are already all set, you just need to pass an instance of TournamentScores() to TournamentResult(Scores:).
By the way, only use lowercases for the first letter of the name of your variables or struct properties: var lastName or TournamentResult(scores:).
I think you are confusing the term "multi-dimensional (array) structures" (which are just arrays nested inside other arrays, like that: [ [1, 2, 3], [2, 3, 4], [3, 4, 5]]) with the struct objects. You are probably not supposed to use structs so extensively here.
Don't hesitate to review the way you decide to use enums, structs, or arrays. Your code may work but will be difficult to read and use (example: how would you access a specific set score if you put all of the set scores in a single String? Why not use an array?)

Multiple Structs Arrays into a single struct type

I'm looking to take two struct arrays of array data, retrieved from multiple sources (web REST calls) and cast them into one singular struct array that I can use on a UITableView.
I originally went down the avenue of creating a multidimensional array, but proved difficult when I had to downcast the types as [AnyObject] and couldn't read into the properties.
Example Data:-
import UIKit
// First Set Of Data
struct Items : Codable{
// List of 'items'
let items : [Item]
}
struct Item: Codable{
// Items inside the array
let id : String
let name : String
let price : String
let imageURL : String
let weight : String
}
// ******** SECOND SET OF DATA
struct SecondSetOfItems : Codable {
let mainFopCollection : MainFopCollection?
}
struct MainFopCollection : Codable {
let sections : [Sections]?
}
struct Sections : Codable {
let id : String?
let fops : [Fops]?
}
struct Fops : Codable {
let sku : String?
let hash : Int?
let product : Product?
}
struct Product : Codable {
let sku : String?
let name : String?
let price : Price?
}
struct Price : Codable {
let current : Double?
let lpp : Bool?
let unit : Unit?
let type : String?
}
struct finalSetOfItems{
let name : String?
let price: String?
}
var array1 = [Items]()
var array2 = [SecondSetOfItems]()
// Originally tried:-
var multiArray = [[AnyObject]]()
multiArray.append(array1 as [AnyObject])
multiArray.append(array2 as [AnyObject])
// This part errors because I can't access this property
let name = multiArray[1][1].name
// Also tried a ternary operator (this part is within cellforrowat in my UITableViewController but I want the data to be dynamic, and not hard coded.
let name = indexPath.section == 0 ? array1![indexPath.row].name : array2![0].sections![0].fops![indexPath.row].product!.name
But then I couldn't access properties of the arrays as the names were too ambiguous, and as I'm using multiple data types it is hard to stipulate what exactly I'm looking for.
The second set of data is considerably more complex (and deeper) than the first set.
Desired Output:-
// Take complex array1 and array2 and change them into a type of finalSetOfItems
var array1 = [Items]()
var array2 = [SecondSetOfItems]()
var arrayOutput = [finalSetOfItems]()
Is there a way i can cast both sets of data into a singular struct type, so the properties can be accessed easier?
The end goal is to cast each array into its own section within a UITableView to denote where the data came from
Apologies if my question is badly worded, I'm still relatively new to Swift. Any help would be greatly appreciated
let firstItemSet = array1.flatMap{
$0.items
}.map{
FinalSetOfItems(name: $0.name, price: $0.price)
}
let secondItemSet = array2.flatMap{
$0.mainFopCollection?.sections ?? []
}.flatMap{
$0.fops ?? []
}.compactMap{
$0.product
}.map{
FinalSetOfItems(name: $0.name, price: $0.price)
}
let finalSet = firstItemSet + secondItemSet

Check if struct string array contains elements of another string array

I created an array of struct elements. These structs get to contain an array of strings. I want to check if these strings happen to be in another array of strings.
How can I do that or what tools should I look into?
I found that I can use a command called "Set", but it doesn't seem to work arrays within a struct.
import UIKit
// Define structure
struct Drink {
var name: String
var content: Array<String>
var amount: Array<Int>
var desc: String
}
// Define drinks
var mojito = Drink(name: "Mojito", content: ["Rum","Club soda"], amount: [4,20], desc: "Summer drink")
var vodkaJuice = Drink(name: "Vodka juice", content: ["Vodka","Juice"], amount: [4,20], desc: "Cheap alcohol")
var list = [mojito,vodkaJuice]
// Define what ingredients you have
var stock = ["Gin", "Vodka", "Juice", "Club soda"]
How do I make a list of drinks I can make from what I have?
Use a Set instead of an array so you can simply do a subset check:
import UIKit
// Define structure
struct drink {
var name : String
var content : Set<String> // we use a Set instead
var amount : Array<Int>
var desc : String
}
// Define drinks
var mojito = drink(name: "Mojito", content: ["Rum","Club soda"], amount: [4,20], desc: "Summer drink")
var vodkaJuice = drink(name: "Vodka juice", content: ["Vodka","Juice"], amount: [4,20], desc: "Cheap alcohol")
var list = [mojito,vodkaJuice]
// Define what ingredients you have
var stock = ["Gin", "Vodka", "Juice", "Club soda"]
// find all instances of drinks where their contents
// are subsets of what you have in stock
let drinks = list.filter { $0.content.isSubset(of: stock) }
The importance of using sets instead of "for-loops inside for-loops" is performance. Set uses an internal hash table to look up an item in an extremely fast fashion. So the overall complexity of your lookups would be O(N.logM) for N items in list and M items in stock.
If you had done it with for loops, its complexity would be O(N.M) which could take longer and consume more battery depending on the number of items you have.
That doesn't mean you should always use sets though. Sets have tradeoffs. They bring in performance but their initial construction is slower and they don't support duplicate items. Use them only in specific cases like this. Never use sets because "they are faster", use them when they solve your specific problem.
I strongly recommend skimming over those additional data structures provided by Swift's runtime so you'll know which one to use and when.

Swift: Accessing array value in array of dictionaries

I am currently struggling with obtaining a value from an array inside an array of dictionaries. Basically I want to grab the first "[0]" from an array stored inside an array of dictionaries. This is basically what I have:
var array = [[String:Any]]()
var hobbies:[String] = []
var dict = [String:Any]()
viewDidLoad Code:
dict["Name"] = "Andreas"
hobbies.append("Football", "Programming")
dict["Hobbies"] = hobbies
array.append(dict)
/// - However, I can only display the name, with the following code:
var name = array[0]["Name"] as! String
But I want to be able to display the first value in the array stored with the name, as well. How is this possible?
And yes; I know there's other options for this approach, but these values are coming from Firebase (child paths) - but I just need to find a way to display the array inside the array of dictionaries.
Thanks in advance.
If you know "Hobbies" is a valid key and its dictionary value is an array of String, then you can directly access the first item in that array with:
let hobby = (array[0]["Hobbies"] as! [String])[0]
but this will crash if "Hobbies" isn't a valid key or if the value isn't [String].
A safer way to access the array would be:
if let hobbies = array[0]["Hobbies"] as? [String] {
print(hobbies[0])
}
If you use a model class/struct things get easier
Given this model struct
struct Person {
let name: String
var hobbies: [String]
}
And this dictionary
var persons = [String:Person]()
This is how you put a person into the dictionary
let andreas = Person(name: "Andreas", hobbies: ["Football", "Programming"])
persons[andreas.name] = Andreas
And this is how you do retrieve it
let aPerson = persons["Andreas"]

Create a multi dimensional array in swift and use the data to populate 3 tableviews

Thanks for taking the time to read my question.
I have experience in php with multidimensional arrays but i am struggling to transfer my knowledge over to swift...
The plan is to create an array like this:
Category1
-- product1
--- description
-- product2
--- description
Category2
-- product1
--- description
-- product2
--- description
Category3
-- product1
--- description
-- product2
--- description
And using 3 tableviews (1 on each viewcontroller) i would like to populate the info:
Tableview1 -- Category list.
Tableview2 -- Product for selected category list.
Tableview3 -- Description for selected.
I can populate 1 tableview using a simple array easily, but how would i:
1) Create the multidimensional array?
2) Count & populate each tableview?
3) Tie each child row to parent rows?
I really hope i am making sense and i am not looking for a free ride, just a helping hand as i am really enjoying learning the swift language!
Kind regards
Rory
Welcome to swift user1263909
How to create an array of strings
let stringArray = [String]()
How to create an array of strings with string literals
let categoryStringArray = ["Category1", "Catagory2", "Catagory3"]
How to create an array of string array's
let arrayOfArray = [[String]]()
how to create an array of string array literals
let a = [["Category1", "Catagory2", "Catagory3"],["product1", "product2", "product3"]]
accessing "Catagory3"
a[0][2]
However this all can get complicated fast. Why not use some structs?
struct Product {
var name:String
var description:String
var price:Double
//ect...
}
use struct to encapsulate data for easy use
let p1 = Product(name: "hat", description: "a hat", price: 2.00)
lets make an array of structs.
let arrOfProduct = [p1,Product(name: "tie", description: "a bow tie", price: 5.00)]
arrOfProduct now has two structs in it a tie and a hat.
How to access hat's price
arrOfProduct[0].price
oh we could also have a struct for catagories, it can contain products as an array
struct Category {
var name:String
var products:[Product]
}
Lets create a Category for Clothes.
let clothes = Category(name: "Clothing", products: arrOfProduct)
lets create another catagory
let food = Category(name: "Food", products: [Product(name: "Candy", description: "Yummy Candy", price: 2.0)])
and finally we can have an array of Catagory
let arrC = [clothes,food]
now I can loop through all catagories in first table view to fill out the cell names
for c in arrC{
print(c.name)
}
when you switch to a new view controller pass it the correct array of products
let productsClicked = arrC[0].products
then on that new view controller loop the products
for p in productsClicked{
print(p.name)
}
if they click a product pass the product to the next view controller and so on.
this will be much easier to maintain as you get new requirments.
Goodluck and welcome to swift. :D

Resources