Check if struct string array contains elements of another string array - arrays

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.

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?)

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.

How to fetch data from a dictionary

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.
}

Swift 4 append items to a specific section in a structured array

I am looking to (per this example) add more items to a specific section of a structured array after creating the initial entry.
struct Zoo {
let section: String
let items: [String]
}
var objects = [Zoo]()
let animals = Zoo(section: "Animals", items: ["Cat","Dog","Mouse"])
let birds = Zoo(section: "Birds", items: ["Crow","Pidgeon","Hawk"])
let reptiles = ["Snake","Lizard"]
objects.append(animals)
objects.append(birds)
// ... varous logic and proccessing where I determine I need
// to add two more items to the animals section...
// trying to extend animals with two more entries.
// this is where I am getting hung up:
objects[0].items.append(reptiles)
Remove the following code
objects[0].items.append(reptiles)
Use this code:
objects[0].items += reptiles
Update for Swift 5:
In Swift 5, this solution will not work and you will get an error like
"Left side of mutating operator isn't mutable: 'items' is a 'let'
constant"
The solution is to change the structure :
struct Zoo {
let section: String
var items: [String]
}

Swift - Sort array similar to another

In my tableview I get my data shown from these arrays:
var offersProduct = [String]()
var offersPrice = [String]()
var savings = [String]()
var shops = [String]()
var imageNames = [String]()
var exDates = [Int]()
var pickedIDs = [String]()
var setDates = [Int]()
Right now the data isn't sorted in any way. So I have 2 questions.
How do I sort arrays after date? Nearest to NSDate first.
I have the array of exDates, witch is an array of Int of timeIntervalSince1970. So i want the closest expiry date to get index 0 of the array.
How do I sort the other arrays like the first one?
So, lets say I have sorted exDates array, that gives me another problem, cause now the expiry date shown, doesn't match with the data from the other arrays. So I need to somehow sort the other arrays, by moving the data in from the same index as in the exDates array. How can that be done?
Hope someone can help.
Your problem is that you use separate entities where single/uniform entity should be used. To achieve what you are trying, you should switch to using class or struct which would encompass all the associated data.
If you proceed with the approach you currently adopt, you are guaranteed to have arbitrary bugs and performing dull operations to synchronize data across all your arrays. So, in essence, having multiple arrays instead of one array of specialized objects is a bad practice.
Consider refactoring your arrays:
struct Entity {
var offerProduct : String
var offerPrice : String
var savings : String
var shop : String
var imageName : String
var exDate : Int
var pickedID : String
var setDate : Int
}
var entities = [Entity]()
Then to sort single array of entities (once filled), you merely set necessary criteria, such as:
entities = entities.sorted(by: {$0.exDate > $1.exDate })
Make a structure:
struct Entry {
let product: String
let price: String
let saving: String
let shop: String
let imageName: String
let exDate: Int
let pickedID: String
let setDate: Int
}
Then use one array of these structures instead of several:
var entries = [Entry]()
You can sort this array easily by one of the structure's fields:
let expiryTimePoint = ...
entries = entries.sorted({
// compare absolute differences of intervals for sorting
return abs($0.exDate - expiryTimeInterval) > abs($1.exDate - expiryTimeInterval)
})

Resources