How to fetch data from a dictionary - arrays

I have a list of dogs and need to fetch certain bits of data. In one case I need the row of names to show in a list, in other cases I need all or parts of the data from a single dog (name, gender, speed). I am fairly certain I should be using an array, although I started with a dictionary. I plan to add more parameters and allow users to add more dogs, so I am trying to find the most expandable option
struct Dog {
var name: String
var gender: String
var speed: Int
}
struct MyDogs {
let myDogs = [
Dog(name: "Saleks", gender: "Male", speed: 50),
Dog(name: "Balto", gender: "Male", speed: 70),
Dog(name: "Mila", gender: "Female", speed: 20)
]
}

WARNING I don't have my IDE available, may have a few syntax errors.
For reference, what you're demonstrating is not a multi-dimensional array. A 3d array is like this.
let some3DArray =
[["Hello", "World"],
["This", "Is", "An"],
["Multidimensional","Array"]]
To access the values in your example, based on what you're asking for you'd do it like so.
//To loop through all the dogs in your array. Useful for your "List"
for dog in yourDogs {
print(" Name: \(dog.name) "
}
// To find a dog based on some property you can do something like this.
let dog = {
for dog in yourDogs {
if dog.name == yourSearchValue {
return dog
} else {
//HANDLE NULL VALUE
//What do you want to happen if NO dog is found?
}
return null
}
}
// You can use the values from the array by accessing it directly via an index.
// This can be done with whatever conditional you need to specifically reach.
let specificDog = dogs[3]
// Once you have your copy of the specific dog you want to access.
// You can then get the values of that object.
let dogName = specificDog .name
let dogGender = specificDog .gender
let dogSpeed = specificDog .speed
Your use-case seems to be on the right track. An array would be useful and provide the most flexibility to add more dogs later down the road. This could be handled very easily for example by doing something like this. You can find out more about that here. Add an element to an array in Swift
var yourDogArray = [Dogs]()
yourDogArray.append(Dog(name: "xxx", gender: "female", speed: 20))
TableView(didSelectRowAt...)
This is a common usage And it works because your list that you populate is populated on an index from 0 to length which means if you select the first item on the list, it will match with your first item in your arrayCollection.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath {
let name = yourDogArray[indexPath.row].name
let gender = yourDogArray[indexPath.row].gender
let speed = yourDogArray[indexPath.row].speed
//Do whatever else you need to do here with your data. In your case you'd
//probably segue to the details view controller and present this data.
//Read up on Segue and Prepare for Segue to pass data between controllers.
}

Related

Looping over and combining Swift array of structs?

I'm trying to create an array of structs (User structs below) where, if the user.name does not yet exist in the array, it appends the user -- but if the user.name is already present ("Mcdonalds" in the below example), then it will simply add the item.amount to the existing struct.
In other words, the below code should create an array of 4 Users, with the User Mcdonalds item.amount totaling 23.44 + 12.33.
I remember doing this sort of thing in JS no sweat, but I can't for the life of me figure out how to do this in Swift. Thanks for any help!
struct User {
var name: String
var amount: Double
}
var user1 = User(name: "Mcdonalds", amount: 23.44)
var user2 = User(name: "Wendys", amount: 15.44)
var user3 = User(name: "Cabanos", amount: 12.22)
var user4 = User(name: "Shell Gas", amount: 23.33)
var user5 = User(name: "Mcdonalds", amount: 12.33)
To loop over the users they'll need to be in an array to start.
Then you can use .reduce(into:) to reduce them into one condensed dictionary (the dictionary will allow you to have a unique key (the name of the user here) so that you don't have duplicate entries). Then you can use .map() to just get the value and not the key of that dictionary so that the final result will be an array of users.
struct User {
var name: String
var amount: Double
}
var users = [
User(name: "Mcdonalds", amount: 23.44),
User(name: "Wendys", amount: 15.44),
User(name: "Cabanos", amount: 12.22),
User(name: "Shell Gas", amount: 23.33),
User(name: "Mcdonalds", amount: 12.33)
]
var reducedUsers = users.reduce(into: [String: User]()) { (result, nextUser) in
if let existing = result[nextUser.name] {
result[nextUser.name] = User(name: nextUser.name, amount: existing.amount + nextUser.amount)
} else {
result[nextUser.name] = nextUser
}
}.map { $0.value }
A clean and swifty way is to write an extension for Array. Swift is highly protocol-oriented, which means you are able to extend any existing system or self-written class with new functions.
This is just a simple implementation, which uses a function to append or update any given user object:
extension Array where Element == User {
/// Appends a not existing user to the array or updates the amount value if user is already present
mutating func appendOrUpdate(_ userObject: Element) {
// Check if user is already in the array
if let index = self.firstIndex(where: { $0.name == userObject.name }) {
// Update the amount
self[index].amount += userObject.amount
}
else {
// Append the user
self.append(userObject)
}
}
}
As the where clause specifies the extension the Element of the array only to be applied when the given object is your User struct, it is only available when you pass in an user object.
Usage:
var userArray: [User] = []
userArray.appenOrUpdate(User(name: "Mcdonalds", amount: 23.44))
userArray.appenOrUpdate(User(name: "Wendys", amount: 15.44))
userArray.appenOrUpdate(User(name: "Cabanos", amount: 12.22))
userArray.appenOrUpdate(User(name: "Shell Gas", amount: 23.33))
userArray.appenOrUpdate(User(name: "Mcdonalds", amount: 12.33))
This will result in an array with just four entries and the double entry 'Mcdonalds' user's amount cumulated.
Note that the function has the mutating keyword in front, as if not you will not be able to modify the array and its entries. This is necessary due the nature of arrays being Structs themselves.
You can also write a function like the know Array's append(contentsOf:) and pass in an array of user objects and loop through them updating or appending the given objects.
Best way is to put this extension in a separate file called Array+User.swift according to best practise naming conventions.
You can read more about extensions in Swift and their power here: https://docs.swift.org/swift-book/LanguageGuide/Extensions.html
Matthew Gray's answer is very good, and can be used for a wide variety of problems that may be more complex than this one. But for this specific problem, it can be done much more simply.
let reducedUsers = users.reduce(into: [:]) { (result, user) in
result[user.name, default: 0] += user.amount
}
.map(User.init)
The point of this is that it tears apart the struct into key and value in a Dictionary, and then reassembles the values into an Array at the end. Swift is smart enough to figure out the type of the [:], so there's no need to specify that.
Note that there is a time-space tradeoff here. This creates a temporary Dictionary that can be very large. If this kind of operation is common and the dataset is large, you should consider storing this data in a Dictionary ([String: User] or [String: Double]) all the time rather than converting back and forth.

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.

Create ordered Array from Dictionary

This is a fundamental thing that I should know but don't know at a deep level and therefore find confusing. Dictionaries seem to be unordered list of keys and values. I want to create an ordered list of keys and values so that I can sort and otherwise keep track of order. I think this means I have to convert the dictionary into a multi-dimensional Array that has the key and value and also an index value ie 0,1,2,3 etc.
If my dictionary looks like the following:
var myScores = [String: Float]()
myScores = ["player1":22,"player2":33]
How do I convert it into an Array where player1:22 is the first element and player2:33 is the second element?
Edit:
Alternative without creating struct is to create an empty array of dictionaries in the form of your dictionary and then append your dictionary to the array.
var myArray = [[String: Float]]()//note double brackets
propArray.append(myScores)
What you want is an array of structs.
struct Score {
let playerName: String
let score: Int
}
let scores = [Score(playerName: "player1", score: 22),
Score(playerName: "player2", score: 33),
]
let sortedScores = scores.sorted(by: { $0.playerName < $1.playerName })
To get key-value pairs out of a dictionary is straightforward, though it forces you to work with tuples, which are not a particularly friendly type. Even so, it's done this way:
let sortedScores = myScores.sorted { $0.key < $1.key }
That will create:
[(key: "player1", value: 22.0), (key: "player2", value: 33.0)]
Or
for (name, score) in myScores.sorted(by: { $0.key < $1.key }) {
print(name, score)
}
You can keep the dictionary as-is. One possible solution is to create an array that contains just the keys in the desired order. Then you can iterate the key array and access the elements of the dictionary.
var myScores: [String:Float] = ["player1":22, "player2":33]
var myPlayers = myScores.keys.sorted()
for player in myPlayers {
let score = myScores[player]
}
This works when you want to show the data in some particular order, such as in a table view.

Generate random Strings without a shuffle and repetition of previous ones?

My code already generates a random String of an array when I press a button, but sometimes a String gets repeated. What do I have to do so that the String "Mango" only gets called again when all the other Strings where already called without using a shuffle, I want to call one String at a time?
Example: "Mango", "Kiwi", "Banana", "Pineapple", "Melon", "Mango", "Kiwi",.....
Here is my code:
var array = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
let fruits = Int(arc4random_uniform(UInt32(array.count)))
print(array[fruits])
In order to avoid repetitions you need to keep track of which fruits have previously been seen. There are several ways to do that an all of the proposed solutions do it in one way or another.
For your specific use case, you will need this tracking to be retained outside of the code executed by the button (in your view controller for example).
Here is a generalized structure that could help with this:
(you can define it inside the view controller if this is a one-time thing or outside of it if you intend to reuse the mechanism elsewhere)
struct RandomItems
{
var items : [String]
var seen = 0
init(_ items:[String])
{ self.items = items }
mutating func next() -> String
{
let index = Int(arc4random_uniform(UInt32(items.count - seen)))
let item = items.remove(at:index)
items.append(item)
seen = (seen + 1) % items.count
return item
}
}
To use it, you would declare a variable (in your VC) that will keep track of your fruits:
var fruits = RandomItems(["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"])
And in the button's code use that variable (fruits) to print a non-repeating fruit name at each execution
func buttonPressed() // <- use your function here
{
print( fruits.next() )
}
You need to implement some logic. It's quite easy if you think harder. Run this in your Playground, or if you fully understand this block of code, you can do this in your project already.
var array = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
var selectedIndices = [Int]()
for _ in 1...20 {
let randomFruitIndex = Int(arc4random_uniform(UInt32(array.count)))
// Print only if not yet printed once
if !selectedIndices.contains(randomFruitIndex) {
print(array[randomFruitIndex])
selectedIndices.append(randomFruitIndex)
}
// Reset
if selectedIndices.count == array.count {
print("----- CLEARING SELECTED INDICES----")
selectedIndices.removeAll()
}
}
So as you can see, we are adding each generated random number (in your case, it's the fruits variable.) into an array of Int. Then if the number of selectedIndices is equal to the count of the array of fruits, clear all the stored selectedIndices.
OUTPUT:
Pineapple
Melon
Mango
Kiwi
Banana
Apple
----- CLEARING SELECTED INDICES----
Mango
Melon
This is an adaption from the accepted answer of the linked topic in my comment:
var source = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
var usedElements = [String]()
func choosePseudoRandomElement() -> String {
if source.count == 0 {
source = usedElements
usedElements = []
}
let randomIndex = Int(arc4random_uniform(UInt32(source.count)))
let randomItem = source[randomIndex]
usedElements.append(randomItem)
source.remove(at: randomIndex)
return randomItem
}
for _ in 1...18 {
print("Item: \(choosePseudoRandomElement())")
}
One potential issue with this solution is that it may happen, that the last element of one complete iteration also occurs as the first element of the second iteration. You can handle that case by comparing the randomly chosen item with the item which was chosen before (use a while loop until the items doesn't match anymore).
Also, this does remove elements from the source array. If you do not want that, create a copy of the source array.

Dictionary that contains multiple values for keys using Swift

is it possible to have a dictionary, that contains multiple values for one key?
For example I have multiple IDs (101,102,103,104...) and all of those IDs have some values (colour: green, number: 4, city: New York). I am new to programming and not sure how should I store it.
I was thinking about custom class:
class myClass: NSObject {
var ID: Int
var colour: String
var number: Int
var city: String
init(ID: Int, colour: String, number: Int, city: String) {
self.ID = ID
self.colour = colour
self.number = number
self.city = city
}
}
And the create some variable var myData = myClass() and just everything there, but then how could I access lets say ID 102 and modify its colour?
So my question how to store data provided in my example with an option to be able select specific ID and modify values belonging to that ID.
A dictionary must contain exactly one value for each key. However, that value could be an array, or a dictionary, or an object etc. etc. so that is no practical restriction.
Since instance of classes are stored as references, the dictionary values could be references to class instances. So you can extract the reference to an instance from the dictionary, and then modify the instance. Be aware that anyone holding a reference to the same instance will see the same changes.
Assuming you have two instances of your class
let item101 = MyClass(ID: 101, colour: "red", number: 4, city: "NewYork")
let item102 = MyClass(ID: 102, colour: "green", number: 7, city: "Chicago")
You can store them in a dictionary using the ID as key
let dictionary = [101: item101, 102: item102]
and retrieve a value by ID with
if let id101 = dictionary[101] {
// use id101
} else {
print("ID 101 not found")
}
or you can use an array
let array = [item101, item102]
and get an item by ID with the filter function
let filtered = array.filter{ $0.ID == 102 }
if !filtered.isEmpty {
let id102 = filtered[0]
} else {
print("ID 102 not found")
}
Declare you id with Array and foreach in that ID
var ID: [Int]

Resources