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

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

Related

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.

How prevent Object.keys() sort?

The problem with the ECMA standard for sort of Object.keys() is known:
Object.keys() handle all keys with integer (example: 168), including integer as strings (example: "168"), as a integer. The result is, both are the same (168 === "168"), and overwrite itself.
var object = {};
object["168"] = 'x';
object[168] = 'y';
Object.keys(object); // Array [ "168" ]
object[Object.keys(object)]; // "y"
Interestingly, all keys (including pure integer keys) are returned as a string.
The ecma262 wrote about this: All keys will be handle as a integer, expect the key is a String but is not an array index.
https://tc39.es/ecma262/#sec-ordinaryownpropertykeys
That should tell us: 168 === "168". A toString() do not solve the problem.
var object = {};
object[[3].toString()] = 'z';
object[[1].toString()] = 'x';
object[[2].toString()] = 'y';
Object.keys(object);
// Array(3) [ "1", "2", "3" ]
Paradoxically, in this case, only integer apply as "enumerable" (it's ignoring array.sort(), that sort also strings with letters.).
My question about this is simple: How can i prevent the sort function in Object.keys()? I have testet the Object.defineProperties(object, 1, {value: "a", enumerable: true/false}), but that mean not realy enumerable in the case of integer or string or integer-like string. It means only should it be counted with or not. It means "counted" like omit (if it false), not "enumerabled" like ascending or descending.
A answere like that is not a good answer: Please use only letters [a-zA-Z] or leastwise a letter at the first position of keyword.
What I want: That the keys are not sorted, but output in the order in which they were entered, whether integer, string or symbol.
Disclaimer: Please solutions only in JavaScript.
Javascript Objects are unordered by their nature. If you need an ordered object-like variable I would suggest using a map.
To achieve what you're looking for with a map instead of object you'd do something like the below:
var map1 = new Map();
map1.set("123", "c");
map1.set(123, "b");
var iterator1 = map1.keys();
var myarray = [];
for (var i = 0; i < map1.size; i++) {
myarray.push(iterator1.next().value);
}
console.log(myarray);
// Array ["123", 123]
Unfortunately it's not compatible with IE and I'm not sure how else you could achieve what you need without it. A quick Google did return something about jQuery maps, though.
If you don't want to use jQuery and still need to support IE some points are below:
Is there anything stopping you using an array rather than JS object to store the data you need? This will retain the order per your requirements unlike objects. You could have an object entry in each iteration which represents the key then use a traditional foreach to obtain them as an array. I.e.
The array:
var test_array = [
{key: 123, value: 'a value here'},
{key: "123", value: 'another value here'}
];
// console.log(test_array);
Getting the keys:
var test_array_keys = [];
test_array.forEach(function(obj) { test_array_keys.push(obj['key']); } );
// console.log(test_array_keys);
Then if you needed to check whether the key exists before adding a new entry (to prevent duplicates) you could do:
function key_exists(key, array)
{
return array.indexOf(key) !== -1;
}
if(key_exists('12345', test_array_keys))
{
// won't get here, this is just for example
console.log('Key 12345 exists in array');
}
else if(key_exists('123', test_array_keys))
{
console.log('Key 123 exists in array');
}
Would that work? If not then the only other suggestion would be keeping a separate array alongside the object which tracks the keys and is updated when an entry is added or removed to/from the object.
Object Keys sorted and store in array
First Creating student Object. then sort by key in object,last keys to store in array
const student={tamil:100, english:55, sci:85,soc:57}
const sortobj =Object.fromEntries(Object.entries(student).sort())
console.log(Object.keys(sortobj))
use map instead of an object.
let map = new Map()
map.set("a", 5)
map.set("d", 6)
map.set("b", 12)
to sort the keys (for example, to update a chart data)
let newMap = new Map([...map.entries()].sort())
let keys = Array.from(newMap.keys()) // ['a','b','d']
let values = Array.from(newMap.values()) // [5,12,6]

Sorting a dictionary by key and converting it into an array

I have a dictionary of prices and quantities. I am getting updates on the price and values multiple times in a second so I don't want to store them in an array because dictionary are much faster.
let mainPriceValDict = [Double:Double]()
The data is coming in as an array of JSON so I am using codable to parse the JSON and put it into a dictionary. When I use the data, it needs to be sorted in ascending and/or descending order because I am looping through each price in order to get to a certain total quantity. The format that the array is in that I am looping through is as follows:
let loopingArray = [PriceQuantityEntry]()
struct PriceQuantityEntry {
let price : Double
let size : Double
}
I want to sort the prices which are the keys in the dictionary above and convert them into an array of PriceQuantityEntry. What is the best way to do this? In ascending and deciding order. I have tried first getting all the keys sorted and then grabbing associated values and putting them into the array in order but this seems like more processing than this task actually requires.
I think the best way to do this would be to put a custom initializer in the struct to convert the dictionary value to a value of type PriceQuantityEntry but I am not exactly sure how that would work with the sorting.
This is what I am currently doing to get it to work. I just feel like there is a more efficient way for it to be done. If you feel like I should keep the structure as an array instead of converting it to a dict, let me know.
loopingArray = self.mainPriceValDict.sorted { $0.0 < $1.0 }.map { PriceQuantityEntry(price: $0.0, size: $0.1) }
If you are getting a lot of updates to individual entries, both a dictionary and an array may cause memory copies of the whole memory structure every time an entry is changed.
I would suggest using objects (classes) instead of structures. This will allow you to use both an array and a dictionary to reference the object instances. The dictionary will provide direct access for updates and the array will allow sequential processing in forward or backward order.
[EDIT] Example:
class PriceQuantityEntry
{
static var all:[PriceQuantityEntry] = []
static var prices:[Double:PriceQuantityEntry] = [:]
var price : Double
var size : Double
init(price:Double, size:Double)
{
self.price = price
self.size = size
PriceQuantityEntry.all.append(self)
// PriceQuantityEntry.all.resort() // on demand or when new prices added
PriceQuantityEntry.prices[price] = self
}
class func update(price:Double, with size:Double)
{
if let instance = PriceQuantityEntry.prices[price]
{ instance.size = size }
else
{
let _ = PriceQuantityEntry(price:price, size:size)
PriceQuantityEntry.resort()
}
}
class func resort()
{
PriceQuantityEntry.all.sort{$0.price < $1.price}
}
}
// if adding multiple initial entries before updates ...
let _ = PriceQuantityEntry(price:1, size:3)
let _ = PriceQuantityEntry(price:1.25, size:2)
let _ = PriceQuantityEntry(price:0.95, size:1)
PriceQuantityEntry.resort()
// for updates ...
PriceQuantityEntry.update(price:1, with: 2)
// going throug list ...
var count:Double = 0
var total:Double = 0
var quantity:Double = 5
for entry in PriceQuantityEntry.all
{
total += min(entry.size,quantity-count) * entry.price
count = min(quantity,count + entry.size)
if count == quantity {break}
}

swift - using .map on struct array

i have a struct array that i want "break up" into smaller arrays that can be called as needed or at least figure out how i can map the items needed off one text value.
the struct:
struct CollectionStruct {
var name : String
var description : String
var title : String
var image : PFFile
var id: String
}
and the array made from the struct
var collectionArray = [CollectionStruct]()
var i = 0
for item in collectionArray {
print(collectionArray[i].name)
i += 1
}
printing partArray[i].name gives the following result:
pk00_pt01
pk00_pt02
pk00_pt03
pk01_pt01
pk01_pt02
pk01_pt03
pk01_pt04
pk01_pt05
pk01_pt06
pk01_pt07
pk01_pt08
this is just some test values but there could be thousands of entries here so i wanted to filter the entire array just by the first 4 characters of [i].name i can achieve this by looping through as above but is this achievable using something like .map?
I wanted to filter the entire array just by the first 4 characters of
[i].name
You can achieve this by filtering the array based on the substring value of the name, as follows:
let filteredArray = collectionArray.filter {
$0.name.substring(to: $0.name.index($0.name.startIndex, offsetBy: 4)).lowercased() == "pk00"
// or instead of "pk00", add the first 4 characters you want to compare
}
filteredArray will be filled based on what is the compared string.
Hope this helped.
If you want to group all data automatically by their name prefix. You could use a reducer to generate a dictionary of grouped items. Something like this:
let groupedData = array.reduce([String: [String]]()) { (dictionary, myStruct) in
let grouper = myStruct.name.substring(to: myStruct.name.index(myStruct.name.startIndex, offsetBy: 4))
var newDictionart = dictionary
if let collectionStructs = newDictionart[grouper] {
newDictionart[grouper] = collectionStructs + [myStruct.name]
} else {
newDictionart[grouper] = [myStruct.name]
}
return newDictionart
}
This will produce a dictionary like this:
[
"pk00": ["pk00_pt01", "pk00_pt02", "pk00_pt03"],
"pk01": ["pk01_pt01", "pk01_pt02", "pk01_pt03", "pk01_pt04", "pk01_pt05", "pk01_pt06", "pk01_pt07"],
"pk02": ["pk02_pt08"]
]
Not sure if i am understanding you correctly but it sounds like you are looking for this...
To create a new array named partArray from an already existing array named collectionArray (that is of type CollectionStruct) you would do...
var partArray = collectionArray.map{$0.name}

iOS Swift: How to find unique members of arrays of different types based on specific attributes

Goal: I have two different classes, and two arrays containing members of each class. Using Swift 2.0, I would like to find the unique members of one array compared to the other based on specific attributes of each class.
Example:
class A {
var name: String
init(name: String) {
self.name = name
}
}
class B {
var title: String
init(title: String) {
self.title = title
}
}
let aArray = [A(name:"1"), A(name:"2"), A(name:"3"), A(name:"4")]
let bArray = [B(title:"1"), B(title:"2"), B(title:"5")]
So, I'm looking for some operation between aArray and bArray which returns the 3rd and 4th element of aArray, because they are uniquely in aArray, where the basis of comparison is the attributes A.name and B.title.
Of course, reversing the order of the operation would pick out the 3rd element of bArray, because it is uniquely in bArray.
I know I can accomplish the goal straightforwardly using a simple for loop, but I was hoping for something more elegant and more optimized. But if a for loop is as fast or faster than anything fancier, I'm happy to use it just as well.
I'm not sure fancy or elegant this code is, but, we could do something like this:
let mappedArray = bArray.map { $0.title }
let filteredArray = aArray.filter { !mappedArray.contains($0.name) }
So when we want the unique elements from aArray, we first map the elements from bArray to get an array of the value we want to actually compare:
let mappedArray = bArray.map { $0.title }
mappedArray is just an array of strings based on the title property of the objects in bArray.
Next, we use the filter method to filter objects from aArray. The filter method returns an array with objects that pass the test in our closure. The test we want to apply is objects that are not contained in the mapped array we just built.
let filteredArray = aArray.filter { !mappedArray.contains($0.name) }
If we want to do it the other way, just change a few things:
let mappedArray = aArray.map { $0.name }
let filteredArray = bArray.filter { !mappedArray.contains($0.title) }

Resources