Cant figure out how to go through array in Swift and find specific data - arrays

Hello I have question about arrays.
I have an array with following data, also I have corresponding Struct for SpiritRelation():
var spiritRelations = [
SpiritRelation(relationName: "Thunder Lantern", relationSpirit1: "Razor", relationSpirit2: "Lamp Genie", relationSpirit3: "", relationSpirit4: "", relationStats: "Double resist +5%, ATK +1600", relationSpiritIcons: ["razor", "genie"]),
SpiritRelation(relationName: "Illusive Fantasy", relationSpirit1: "Heavenly Maiden", relationSpirit2: "Lamp Genie", relationSpirit3: "", relationSpirit4: "", relationStats: "Excellent strike +15%, Dmg Penetration +15%, Max HP +11500", relationSpiritIcons: ["maiden", "genie"]),
SpiritRelation(relationName: "Grand Demonlord Gathering", relationSpirit1: "Sand Golem", relationSpirit2: "Lamp Genie", relationSpirit3: "", relationSpirit4: "", relationStats: "Excellency Resist +20%, Double Dmg +5%, ATK +1600", relationSpiritIcons: ["golem", "genie"])
}
array which contains data which will be selected by user:
var selectedSpiritsForRelation = [String]()
array of type String because I put there values which corresponds to image names in Assets. I need that to display images
array where I want to keep found relations and use it to display all found relationStats in UI
var foundRelations = [SpiritRelation]()
My problems is:
lets say user has selected 2 spirits for example: selectedSpiritsForRelation["golem", "genie"]
I’m able to find and save correctly found relation by
let result3 = spiritRelations.filter{$0.relationSpiritIcons == (selectedSpiritsForRelation) } // = 3rd relation in spiritRelations[]
foundRelations.append(contentsOf: result3)
but after user select another one spirit and array become: selectedSpiritsForRelation["golem", "genie", "maiden"]
same code as for result3 does not work anymore, because how I understand it tries to filter exactly combination of 3, but my expectation is that 2nd relation from spiritRelation[] will be found also
and here is my problem, I cant figure out how to correctly go through spiritRelations[] and find all relations related to selectedSpiritsForRelation[] every time user selects new spirit

You need to use allSatisfy in your filter by checking that all relationSpiritIcons elements exists in selectedSpiritsForRelation
foundRelations = spiritRelations.filter {
$0.relationSpiritIcons.allSatisfy { icon in
selectedSpiritsForRelation.contains(icon)
}
}

Related

Is there a way of reading from sub arrays?

I am currently building an iOS application that stores user added products using Google Firestore. Each product that is added is concatenated into a single, user specific "products" array (as shown below - despite having separate numbers they are part of the same array but separated in the UI by Google to show each individual sub-array more clearly)
I use the following syntax to return the data from the first sub-array of the "products" field in the database
let group_array = document["product"] as? [String] ?? [""]
if (group_array.count) == 1 {
let productName1 = group_array.first ?? "No data to display :("`
self.tableViewData =
[cellData(opened: false, title: "Item 1", sectionData: [productName1])]
}
It is returned in the following format:
Product Name: 1, Listing Price: 3, A brief description: 4, Product URL: 2, Listing active until: 21/04/2021 10:22:17
However I am trying to query each of the individual sections of this sub array, so for example, I can return "Product Name: 1" instead of the whole sub-array. As let productName1 = group_array.first is used to return the first sub-array, I have tried let productName1 = group_array.first[0] to try and return the first value in this sub-array however I receive the following error:
Cannot infer contextual base in reference to member 'first'
So my question is, referring to the image from my database (at the top of my question), if I wanted to just return "Product Name: 1" from the example sub-array, is this possible and if so, how would I extract it?
I would reconsider storing the products as long strings that need to be parsed out because I suspect there are more efficient, and less error-prone, patterns. However, this pattern is how JSON works so if this is how you want to organize product data, let's go with it and solve your problem.
let productRaw = "Product Name: 1, Listing Price: 3, A brief description: 4, Product URL: 2, Listing active until: 21/04/2021 10:22:17"
First thing you can do is parse the string into an array of components:
let componentsRaw = productRaw.components(separatedBy: ", ")
The result:
["Product Name: 1", "Listing Price: 3", "A brief description: 4", "Product URL: 2", "Listing active until: 21/04/2021 10:22:17"]
Then you can search this array using substrings but for efficiency, let's translate it into a dictionary for easier access:
var product = [String: String]()
for component in componentsRaw {
let keyVal = component.components(separatedBy: ": ")
product[keyVal[0]] = keyVal[1]
}
The result:
["Listing active until": "21/04/2021 10:22:17", "A brief description": "4", "Product Name": "1", "Product URL": "2", "Listing Price": "3"]
And then simply find the product by its key:
if let productName = product["Product Name"] {
print(productName)
} else {
print("not found")
}
There are lots of caveats here. The product string must always be uniform in that commas and colons must always adhere to this strict formatting. If product names have colons and commas, this will not work. You can modify this to handle those cases but it could turn into a bowl of spaghetti pretty quickly, which is also why I suggest going with a different data pattern altogether. You can also explore other methods of translating the array into a dictionary such as with reduce or grouping but there are big-O performance warnings. But this would be a good starting point if this is the road you want to go down.
All that said, if you truly want to use this data pattern, consider adding a delimiter to the product string. For example, a custom delimiter would greatly reduce the need for handling edge cases:
let productRaw = "Product Name: 1**Listing Price: 3**A brief description: 4**Product URL: 2**Listing active until: 21/04/2021 10:22:17"
With a delimiter like **, the values can contain commas without worry. But for complete safety (and efficiency), I would add a second delimiter so that values can contain commas or colons:
let productRaw = "name$$1**price$$3**description$$4**url$$2**expy$$21/04/2021 10:22:17"
With this string, you can much more safely parse the components by ** and the value from the key by $$. And it would look something like this:
let productRaw = "name$$1**price$$3**description$$4**url$$2**expy$$21/04/2021 10:22:17"
let componentsRaw = productRaw.components(separatedBy: "**")
var product = [String: String]()
for component in componentsRaw {
let keyVal = component.components(separatedBy: "$$")
product[keyVal[0]] = keyVal[1]
}
if let productName = product["name"] {
print(productName)
} else {
print("not found")
}

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

Find objects that include an array that contains all elements of a second array

I'm trying to filter a set of objects based on values in one of their elements based on another array. I've got it working with filter just fine if the search is "OR" - it returns give me all objects for which at least one of the strings in the search array is found.
But I can't figure out how to make it work as an AND search - returning only the objects that match ALL of the strings in the search array.
Example:
struct Schedule {
let title: String
let classTypes: [String]
}
let schedule1 = Schedule(title: "One", classTypes: ["math","english","chemistry","drama"])
let schedule2 = Schedule(title: "Two", classTypes: ["pe","math","biology"])
let schedule3 = Schedule(title: "Three", classTypes: ["english","history","math","art"])
let schedules = [schedule1, schedule2, schedule3]
let searchArray = ["math", "english"]
//works for OR - "math" or "english"
var filteredSchedules = schedules.filter { $0.classTypes.contains(where: { searchArray.contains($0) }) }
I'd like to find a way for it to use the same search array
let searchArray = ["math", "english"]
But only return items 1 & 3 - as they both have BOTH math and english in the list.
There are good examples of AND conditions when the AND is across different search criteria: car type and colour - but I've been unable to find an example where the criteria are dynamically based on items in an array. For context, I could have dozens of schedules with 20+ class types.
You can work with a Set, isSubset will return true if the schedules element contains all elements of the searchSet
let searchSet = Set(searchArray)
var filteredSchedules = schedules.filter { searchSet.isSubset(of: $0.classTypes) }
As suggested by #LeoDabus it might be worth changing the type of classTypes to Set instead of arrays (if order doesn't matter) since they seems to be unique and then the filtering can be done in the opposite way without the need to convert searchArray each time
var filteredSchedules = schedules.filter { $0.classTypes.isSuperset(of: searchArray) }

I want to filter array

I got two arrays from API call,
One is nameArray - which contains recipe names ( menuNameArray = ["pohe","bread","cheese chilli toast"]
And another array - which contains prices of those recipes (menuPriceArray = ["10", "40", "120"].
I have got theses two arrays from API call.
I am displaying both the arrays on the table view & I am searching through the menuNamesArray because I want to search by recipe names.
I am getting recipe names by searching those from menuNameArray. Now I want menuPriceArray to get updated also according to searched menuName Array.
means if I search for "bread" then I must get price value as "40" and accordingly for other names also.
How should I perform such filtering on the second array?
My code Snippet -
//MARK: UISearch result update delegate
func updateSearchResults(for searchController: UISearchController) {
// here arrFilter is the resulting array to sotre searched items from menuNamesArray
arrFilter.removeAll(keepingCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (menuNamesArray as NSArray).filtered(using: searchPredicate)
let result = menuPriceArray.firstIndex(of: array.startIndex)
arrFilter = array as! [String]
self.tblSearch.reloadData()
//here now I got the searched menu names, Now I want prices for searched menu names from menuPrice Array..
}
Never use multiple arrays as data source.
Swift is an object oriented language. Take advantage of it.
Create a struct
struct Menu {
let name : String
let price : Double
}
and a data source array
var menues = [Menu]()
Filter the array by name and get the price, pretty easy and straightforward
if let foundMenu = menues.filter(where: {$0.name == "bread"}) {
print(foundMenu.price)
}
You can merge the two arrays into one array of dictionary elements.

Sorted array: how to get position before and after using name? as3

I have been working on a project and Stack Overflow has helped me with a few problems so far, so I am very thankful!
My question is this:
I have an array like this:
var records:Object = {};
var arr:Array = [
records["nh"] = { medinc:66303, statename:"New Hampshire"},
records["ct"] = { medinc:65958, statename:"Connecticut"},
records["nj"] = { medinc:65173, statename:"New Jersey"},
records["md"] = { medinc:64596, statename:"Maryland"},
etc... for all 50 states. And then I have the array sorted reverse numerically (descending) like this:
arr.sortOn("medinc", Array.NUMERIC);
arr.reverse();
Can I call the name of the record (i.e. "nj" for new jersey) and then get the value from the numeric position above and below the record in the array?
Basically, medinc is medium income of US states, and I am trying to show a ranking system... a user would click Texas for example, and it would show the medinc value for Texas, along with the state the ranks one position below and the state that ranks one position above in the array.
Thanks for your help!
If you know the object, you can use the array.indexOf().
var index:int = records.indexOf(records["nj"]);
var above:Object;
var below:Object;
if(index + 1 < records.length){ //make sure your not already at the top
above = records[index+1];
}
if(index > 0){ //make sure your not already at the bottom
below = records[index-1];
}
I think this is the answer based on my understanding of your data.
var index:int = arr.indexOf(records["nh"]);
That will get you the index of the record that was clicked on and then for find the ones below and above just:
var clickedRecord:Object = arr[index]
var higherRecord:Object = arr[index++]
var lowerRecord:Object = arr[index--]
Hope that answers your question
Do you really need records to be hash?
If no, you can simply move key to record field and change records to simple array:
var records: Array = new Array();
records.push({ short: "nh", medinc:66303, statename:"New Hampshire"}),
records.push({ short: "ct", medinc:65958, statename:"Connecticut"}),
....
This gives you opportunity to create class for State, change Array to Vector and make all of this type-safe, what is always good.
If you really need those keys, you can add objects like above (with "short" field) in the same way you are doing it now (maybe using some helper function which will help to avoid typing shortname twice, like addState(records, data) { records[data.short] = data }).
Finally, you can also keep those records in two objects (or an object and an array or whatever you need). This will not be expensive, if you will create state object once and keep references in array/object/vector. It would be nice idea if you need states sorted on different keys often.
This is not really a good way to have your data set up - too much typing (you are repeating "records", "medinc", "statename" over and over again, while you definitely could've avoided it, for example:
var records:Array = [];
var states:Array = ["nh", "ct", "nj" ... ];
var statenames:Array = ["New Hampshire", "Connecticut", "New Jersey" ... ];
var medincs:Array = [66303, 65958, 65173 ... ];
var hash:Object = { };
function addState(state:String, medinc:int, statename:String, hash:Object):Object
{
return hash[state] = { medinc: medinc, statename: statename };
}
for (var i:int; i < 50; i++)
{
records[i] = addState(states[i], medincs[i], statenames[i], hash);
}
While you have done it already the way you did, that's not essential, but this could've saved you some keystrokes, if you haven't...
Now, onto your search problem - first of all, true, it would be worth to sort the array before you search, but if you need to search an array by the value of the parameter it was sorted on, there is a better algorithm for that. That is, if given the data in your example, your specific task was to find out in what state the income is 65958, then, knowing that array is sorted on income you could employ binary search.
Now, for the example with 50 states the difference will not be noticeable, unless you do it some hundreds of thousands times per second, but in general, the binary search would be the way to go.
If the article in Wiki looks too long to read ;) the idea behind the binary search is that at first you guess that the searched value is exactly in the middle of the array - you try that assumption and if you guessed correct, return the index you just found, else - you select the interval containing the searched value (either one half of the array remaining) and do so until you either find the value, or check the same index - which would mean that the value is not found). This reduces asymptotic complexity of the algorithm from O(n) to O(log n).
Now, if your goal was to find the correspondence between the income and the state, but it wasn't important how that scales with other states (i.e. the index in the array is not important), you could have another hash table, where the income would be the key, and the state information object would be the value, using my example above:
function addState(state:String, medinc:int, statename:String,
hash:Object, incomeHash:Object):Object
{
return incomeHash[medinc] =
hash[state] = { medinc: medinc, statename: statename };
}
Then incomeHash[medinc] would give you the state by income in O(1) time.

Resources