How to initialize a Dictionary with structure components in Swift - swift

I have the following structure:
struct song {
var songnum: Int = 0
var name = "not defined"
var lyrics_wo_chords = NSMutableAttributedString()
var lyrics_w_chords = NSMutableAttributedString()
var favorite = false
}
And I'm trying to make a dictionary var songs = [String: [song]]()
where the the String is the name of the Songbook and the [song]
is an array of structures called song that hold the individual structure members
I've tried this songs["SongBook name"] = song.self as? [song] to add a new Key to the Dictionary. But otherwise, i have no idea how i would initialize it.
Also, when i append the array of the Key:
songs["SongBook name"]?.append(song(
songnum: 1,
name: "Name",
lyrics_o_chords: NSMutableAttributedString(string:"No Chords"),
lyrics_w_chords: NSMutableAttributedString(string: "With Chords"),
favorite:
false))`
the dictionary returned is nil
Any help would be much appreciated, thank you

Your value in your dictionary is an array of song (that is [song]), so you need to put [ ] around your value to make it into an array of song:
songs["SongBook name"] = [song(
songnum: 1,
name: "Name",
lyrics_wo_chords: NSMutableAttributedString(string:"No Chords"),
lyrics_w_chords: NSMutableAttributedString(string: "With Chords"),
favorite: false)
]
Structure and class names should be capitalized, so use Song instead of song when defining your structure.

Related

How to create nested dictionary elements in Swift?

I want to create a variable which stores this:
["messageCode": API_200, "data": {
activities = (
{
action = 1;
state = 1;
}
);
messages = (
{
body = hi;
// ...
}
);
}, "message": ]
What I have done is this:
var fullDict: Dictionary<String, AnyObject> = [:]
fullDict["messageCode"] = "API_200" as AnyObject
var data: Dictionary<String, AnyObject> = [:]
fullDict ["data"] = data as AnyObject
Is this way is correct and how I can add activities?
I would suggest to go with creating a custom Model:
struct Model {
var messageCode: String
var data: MyData
var message: String
}
struct MyData {
let activities: [Activity]
let messages: [Message]
}
struct Activity {
var action: Int
var state: Int
}
struct Message {
var body: String
// ...
}
Thus you could use it as:
let data = MyData(activities: [Activity(action: 1, state: 1)], messages: [Message(body: "hi")])
let myModel = Model(messageCode: "API_200", data: data, message: "")
However, if you -for some reason- have to declare it as a dictionary, it could be something like this:
let myDict: [String: Any] = [
"messageCode": "API_200",
"data": ["activities": [["action": 1, "state": 1]],
"messages": [["body": "hi"]]
],
"message": ""
]
which means that myDict is a dictionary contains:
messageCode string.
data as nested dictionary, which contains:
activities array of dictionaries (array of [String: Int]).
messages array of dictionaries (array of [String: String]).
message string.
One of the simplest reasons why you should go with the modeling approach is because when it comes to read from myModel, all you have to do is to use the dot . notation. Unlike working with it as a dictionary, you would have to case its values which could be a headache for some point. For instance, let's say that we want to access the first message body in data messages array:
Model:
myModel.data.messages.first?.body
Dictionary:
if let data = myDict["data"] as? [String: [[String: Any]]],
let messages = data["messages"] as? [[String: String]],
let body = messages.first?["body"] {
print(body)
}
Since you explicitly want it as [String:AnyObject]:
var dict: [String:AnyObject] = ["messageCode":"API_200" as AnyObject,
"data": ["activities": [["action":1,
"state":1]],
"messages": [["body":"hi"]]] as AnyObject,
"message": "" as AnyObject]
Basically all the root values should be typecasted as AnyObject
Or the long way:
//Activities is as Array of dictionary with Int values
var activities = [[String:Int]]()
activities.append(["action": 1,
"state": 1])
//Messages is an Array of string
var messages = [[String:String]]()
messages.append(["body" : "hi"])
//Data is dictionary containing activities and messages
var data = [String:Any]()
data["activities"] = activities
data["messages"] = messages
//Finally your base dictionary
var dict = [String:AnyObject]()
dict["messageCode"] = "API_200" as AnyObject
dict["data"] = data as AnyObject
dict["message"] = "" as AnyObject
print(dict)
Parsing this to get your data back will be hell; with all the type casts and all.
Example (lets capture action):
let action = ((dict["data"] as? [String:Any])?["activities"] as? [String:Int])?.first?.value
As you can see you need to typecast at every level. This is the problem with using dictionaries in Swift. Too much cruft.
Sure, you could use a third-party library like SwiftyJSON to reduce the above to:
let action = dict["data"]["activities"][0]["action"]
But do you want a dependency just for something as simple as this?
Instead...
If your structure is defined then create models instead; as Ahmad F's answer suggests. It will be more readable, maintainable and flexible.
...but since you asked, this is how one would do it with pure Dictionary elements.

Initiate empty Swift array of dictionaries

I currently have this:
var locations = [
["location": "New York", "temp": "2 °C", "wind": "3 m/s"]
]
And I add stuff to this with locations.append(). It works great!
However, I don't want there to be a default entry. So I tried
var locations = [] // Not working.
var locations = [] as NSArray
var locations = [] as NSMutableArray
var locations = [] as NSDictionary
var locations = [] as NSMutableDictionary
var locations = [:] as ... everything
var locations = [APIData]
Feels like I've tried everything but I still get countless errors whatever I try. At this stage I'm even surprised my default locations is working.
How do I solve this? How do I make locations empty to start with?
If we assume you try to initialize a array of dictionary with key String and value String you should:
var locations: [[String: String]] = []
then you could do:
locations.append(["location": "New York", "temp": "2 °C", "wind": "3 m/s"])
To create an array or dictionary you should know the type you need.
With
var array: [String]
you declare a variable to contain an array of strings. To initialize it, you can use
var array: [String] = [String]()
You can omit the : [String] because the compiler can automatically detect the type in this case.
To create a dictionary you can do the same, but you need to define both, the key and the value type:
var dictionary = [String: String]()
Now, what you ask for is an array of dictionaries, so you need to combine both to
var arrayOfDictionaries = [[String: String]]()

filter dictionary of type [String: [object]] for specific key in object arrays

I'm showing users a tableview that looks like the contacts view in the sense that the data source is a dictionary of type [String: [User]] and so I'm showing section headers with the first letter of the Users name.
Now, I want to allow them to search by the users first name... but I can't get this to work.
These are the dictionaries... the friends will hold data like this ["A" :[Users whose first name start with an A]] and so on
var friends = [String: [User]]()
var filteredFriends = [String: [User]]()
This is my filtering code with searchText being the first name I want to search by
self.filteredFriends = self.friends.filter{friend in
return friend.firstName.lowercased().contains(searchText)}
What I want to do is make filteredFriends have all of the values of friends whose users first names start with the text.
How do I make this work with a dictionary like mine?
Thanks
More Info:
Class User {
var firstName: String?
init(name: String){
self.firstName = name
}
}
Sample scenario:
friends = ["A" : [User(name: "Anthony), User(name: "Arnold")], "B" : [User(name: "Barry")]]
filteredFriends = friends
searchText = "an"
Desired filteredFriends (end result) = ["A" : [User(name: "Anthony)]]
There's no simple way. You just have to cycle through the whole dictionary, like this:
// these are your initial conditions
class User {
let firstName: String
init(name: String) {
self.firstName = name
}
}
let friends : [String:[User]] =
["A" : [User(name: "Anthony"), User(name: "Arnold")],
"B" : [User(name: "Barry")]]
let searchText = "an"
// and here we go...
var filteredFriends = [String:[User]]()
for entry in friends {
let filt = entry.value.filter{$0.firstName.lowercased().hasPrefix(searchText)}
if filt.count > 0 {
filteredFriends[entry.key] = filt
}
}
Now filteredFriends contains the desired result.
Ideally, you would use map to transform the input dictionary to the output dictionary, by filtering each of its values (User arrays). Unfortunately, Dicationary.map returns [(Key, Value)] (an array of `(Key, Value) tuples), not a new Dictionary. I'll take the more conventional, iterative approach instead.
Note: For those who are about to circle jerk around using functional styles everywhere, who are about to suggest using reduce: no. Pull out a profiler, and look at how slow and power-consuming it is to generate dictionaries with reduce. Especially important on a mobile device with limited battery.
Here's how I would do it:
let allFriends = [String: [User]]()
var filteredFriends = [String: [User]]()
let searchText = "a".lowercased()
for (initial, allFriendsWithInitial) in allFriends {
if initial.lowercased() == searchText {
// don't even have to bother filtering `friends`
filteredFriends[initial] = allFriendsWithInitial
}
let filteredFriendsWithInitial = allFriendsWithInitial.filter{
$0.firstName.lowercased().contains(searchText)
}
if !filteredFriendsWithInitial.isEmpty {
filteredFriends[initial] = filteredFriendsWithInitial
}
}
print(filteredFriends)

Dynamic Struct creation in Swift - based on user input

I am trying to create dynamic struct in swift based on user input.
struct Diagnosis {
var diagName = String() // Name of the diagnosis
var diagSymptoms = [String]() // Symptoms of the diagnosis in a ranked array to identify prevelancy
var diagSpecialization = [String]() // the specializations which would mostly encounter this diagnosis
var diagRank = Int() // the overall rank of the diagnosis
var diagSynonoms = [String]() // the other name thru which the same diagnosis is called / referred.
// func init(diagName: String(),diagSymptoms: [String](),diagSpecialization: [String](),diagSynonoms: [String]())
init( let pasdiagName: String,let pasdiagSymptoms:Array<String>) {
self.diagName = pasdiagName
self.diagSymptoms = pasdiagSymptoms
}
}
var maleria = Diagnosis(pasdiagName: "Maleria",pasdiagSymptoms: ["fever","chill","body pain"])
The above creates the structure maleria - But in future I want to have the input from user and create a structure for that inputted string
var abc = "typhoid"
let valueof(abc) = Diagnosis()
The value of function is something I just put here arbitrarily to make my explanation clear.
I know I could do this in python and I am new to swift. Thanks in advance for the help.
As #Wain suggested, you should use a Dictionary.
This is how you create a mutable dictionary where the key is a String and the value can be any type.
var dict = [String:Any]()
This is how you put key/value pairs into the dictionary
dict["year"] = 2016
dict["word"] = "hello"
dict["words"] = ["hello", "world"]
And this is how you extract a value and use it
if let word = dict["word"] as? String {
print(word) // prints "hello"
}

Adding additional items to dictionary

Would like to add John together with Peter in this combination:
var myData0: [String: String] = ["Item": "Milk", "Description": "Milk is white", "DrinksMilk": "Peter"]
myData0["DrinksMilk"] = "John"
println(myData0)
Println gives only John back instead Peter AND John. Is there a way to add John without overwriting Peter?
If you want to keep a string value of dictionary, you can do like that
var myData0: [String: String] = ["Item": "Milk", "Description": "Milk is white", "DrinksMilk": "Peter"]
myData0["DrinksMilk"] = myData0["DrinksMilk"]! + ", John"
println(myData0)
If not, you can change the type of dictionary value to AnyObject like that :
var myData0: [String: AnyObject] = ["Item": "Milk", "Description": "Milk is white", "DrinksMilk": "Peter"]
myData0["DrinksMilk"] = [myData0["DrinksMilk"]!, "John"]
println(myData0)
It appears that you are attempting to encode a data type into a dictionary. A better approach, not knowing anything more about your specific problem, is to define a class for this:
class Drink {
var name:String
var desc:String
var drinkers:[Person]
// ...
func addDrinker (person:Person) {
drinkers.append(person)
}
}
You've declared your dictionary to hold values of type String but, as you describe, now you want the dictionary values to be String or Array. The common type of these type value types is Any (it can be AnyObject if you've imported Foundation; but that is a Swift anomaly). You will then need to 'fight' the type system to coerce the "DrinksMilk" field as a modifiable array and to add "John".
// First extract `"DrinksMilk"` and force it to a mutating array:
var drinkers:[String] = myData0["DrinksMilk"]! as [String]
// Add the new drinker
drinkers.append("John")
// Reassign back into data
myData0["DrinksMilk"] = drinkers