Filter nested arrays with objects - arrays

I have an array of Categories. Each Category instance has offers property.
class Category {
var offers : [Offer]?
var title : String?
var id : Int?
}
class Offer {
var type : String?
}
//global variable
var categories = [ categ1, categ2, ...]
How can I filter categories by offer.type ?
I already have tried:
return categories.map { (category) -> Category in
let offers = category.offers?.filter { $0.type == myType }
category.offers = offers
return category
}
It works but after calling function second time array becomes empty. Probably because offers were rewritten?
Then I have tried this (produced same wrong result):
var resultCategories = [Category]()
for category in categories {
guard let offers = category.offers else { continue }
var newOffers = [Offer]()
for offer in offers {
if offer.type == myType {
newOffers.append(offer)
}
}
category.offers = newOffers
resultCategories.append(category)
}
return resultCategories

You should simply filter all categories that have no offers equals to your type. You can achieve that by:
filter all your categories and
inside the filter check if current offers contains the myType
Code:
let filtered = categories.filter { category in
category.offers?.contains(where: { $0.type == myType }) ?? false
}
And note, that category.offers?.[...] is optional value, so the ?? false returns false as result if left part is nil.
UPD.
But I expected that categories will have only offers with type = "A". Maybe I did not described the question accurately.
You can achieve that by creating a new Category.
let filtered = categories.compactMap { category -> Category? in
guard let offers = category.offers?.filter({ $0.type == "A" }) else { return nil }
let other = Category()
other.offers = offers
return other
}
Also note, i'm using compactMap. It allows me to filter categories with empty or nil offers out.

You can use simple and easy filter(functional programming) instead of for-loop.
First filter category then check offers contains particular type or not(using equal to condition)
let data = categories.filter { ($0.offers?.contains(where: {$0.type == "yourtype"})) ?? false
}

If you want multiple filter, like one field from one model and second field from nested array, please che
let searchText = "a"
let filteredCategoryList = list.filter { category in
let categoryFilter = category.title?.range(of: searchText, options: .caseInsensitive) != nil
let offerFilter = category.offers?.contains(where: { $0.type?.range(of: searchText, options: .caseInsensitive) != nil
})
return categoryFilter || offerFilter ?? false
}

Related

Swift: Filter a dictionary key from a struct from an array, which is optional

struct Test {
var title: String
var message: [String?: String?]
init(title: String, message: [String?:String?]) {
self.title = title
self.message = message
}
}
var cases = [
Test(title: "1", message: ["tag1": nil]),
Test(title: "2", message: ["tag2": "preview2"]),
Test(title: "3", message: [nil:nil]),
Test(title: "4", message: ["tag1":"preview4"])
]
Now, I want:
An array with all keys from message property from cases - tag1 and tag2 (no nils in it). I just tried everything I know, I couldn't do it. Tried with filtering cases, got optionals.
There are no previews without a tag, so there is no need for an array with them. I only need a list with the tags, in order to sort it and show the relevant previews from the cases. That's why I need to know a way how to access these previews from the cases. Let's say in a UITableView:
cell.previewLabel?.text = cases[indexPath.row].preview[//No idea what here]
Of course, a dictionary with [tags: previews] would also be perfect!
Thanks in advance! I hope what I want is possible.
Here is an array that only contains elements from cases that have all keys and values not nil :
let filtered = cases.filter { test in
return test.message.allSatisfy({ entry in
return entry.key != nil && entry.value != nil
})
}
Or using the shorthand notation :
let filtered = cases.filter {
$0.message.allSatisfy({
$0.key != nil && $0.value != nil
})
}
With structs there is a default initializer, so you can write your Test struct this way:
struct Test {
var title: String
var message: [String?: String?]
}
It's not completely clear to me what you're attempting to do, however, this will filter your cases array to only Test objects that contain non-nil values in the message dictionary:
let nonNil = cases.filter { (test) -> Bool in
return Array(test.message.values).filter({ (value) -> Bool in
return value == nil
}).count <= 0
}
The variable nonNil now contains the Test objects where title is "2" and title is "4".
You could further filter that if you want a [tags:preview] dictionary. Something like this would do that:
let tags = nonNil.map( { $0.message} ).flatMap { $0 }.reduce([String:String]()) { (accumulator, current) -> [String:String] in
guard let key = current.key, let value = current.value else { return accumulator }
var accum = accumulator
accum.updateValue(value, forKey: key)
return accum
}
The tags dictionary now contains: ["tag1": "preview4", "tag2": "preview2"]

How to get a tuple from two lists of elements

I have two lists of UIViews, some of that UIViews have an accessibilityIdentifier most of them are nil.
I'm searching for a way to generate a tuple(or something) with the two UIViews from the lists that have the same accessibilityIdentifier.
The lists are not sorted or something.
Is there a way to not iterate multiple times through the second list to find every pair?
for view in firstViewList {
if view.accessibilityIdentifier != nil {
for secondView in secondViewList {
if secondView.accessibilityIdentifier != nil && secondView.accessibilityIdentifier == view.accessibilityIdentifier {
viewPairs.append((firstView: view, secondView: secondView))
}
}
}
}
I think this is not very efficient.
Make a dict that indexes both view lists by their ID, filter out the ones where the ID is nil, and then use the keys common to both dicts to create a new dict that indexes pairs of same-id views.
Here's a rough example (which I haven't compiled myself).
func makeDictByAccessibilityID(_ views: [UIView]) -> [AccessibilityID: UIView] {
return Dictionary(uniqueKeysWithValues:
firstViewList
.lazy
.map { (id: $0.accessibilityIdentifier, view: $0) }
.filter { $0.id != nil }
)
}
viewsByAccessibilityID1 = makeDictByAccessibilityID(firstViewList)
viewsByAccessibilityID2 = makeDictByAccessibilityID(secondViewList)
commonIDs = Set(viewsByAccessibilityID1.keys).intersecting(
Set(viewsByAccessibilityID2.keys)
)
let viewPairsByAccessibilityID = Dictionary(uniqueKeysWithValues:
commonIDs.lazy.map { id in
// Justified force unwrap, because we specifically defined the keys as being available in both dicts.
(key: id, viewPair: (viewsByAccessibilityID1[id]!, viewsByAccessibilityID2[id]!))
}
}
This runs in O(n) time, which is the best you can get for this problem.
I think you should first filtered your two arrays from the nil value then you can do like this
let tempfirstViewList = firstViewList.filter { (view) -> Bool in
view.accessibilityIdentifier != nil
}
var tempsecondViewList = secondViewList.filter { (view) -> Bool in
view.accessibilityIdentifier != nil
}
tempfirstViewList.forEach { (view) in
let filterdSecondViewList = tempsecondViewList.filter { (secondView) -> Bool in
secondView.accessibilityIdentifier == view.accessibilityIdentifier
}
if let sView = filterdSecondViewList.first {
viewPairs.append((firstView: view, secondView: sView))
//after add it to your tuple remove it from the temp array to not loop throw it again
if let index = tempsecondViewList.firstIndex(of: sView) {
tempsecondViewList.remove(at: index)
}
}
}
This solution creates an array of tuples with views from the first and second list respectively
var viewArray: [(UIView, UIView)]
firstViewList.forEach( { view in
if let identifier = view.accessibilityIdentifier {
let arr = secondViewList.filter( {$0.accessibilityIdentifier == identifier} )
arr.forEach( { viewArray.append((view, $0))})
}
})

Filter Custom Array time optimization

I have to filter array of articles based on a keyword(string) in article's description. Filtering is taking 2-3 seconds on 1500+ elements's array of type Article and 2000+ words in each article description.
I am using below code; i also tried predicate but didn't worked for me.
let searchResult = articlesList.filter {
let article = $0
let filterByName = article.title.lowercased().range(of: text.lowercased())
let filterByDescription = article.body.lowercased().range(of: (text.lowercased()))
if ((filterByName != nil) || filterByDescription != nil) {
if !articlesList.contains(article) {
articlesList.append(article)
}
}
else {
let index = articlesList.index(of: article)
if index != nil {
articlesList.remove(at: index!)
}
}
return false
}
I want filter time max to 0.3 seconds.
This is probably a bit lot faster
let searchResult = articles.filter{ $0.title.range(of: text, options: .caseInsensitive) != nil
|| $0.body.range(of: text, options: .caseInsensitive) != nil }
The problem is that you perform changes on the collection you are filtering. You shouldn't have articleList.remove() and articleList.append() in the filter function. The way filter works is that you just return true for every element that you want to keep in the filtered collection (in your case searchResult) and false for the rest.
Try this code:
let searchResult = articlesList.filter { article in
let filterByName = article.title!.lowercased().range(of: text.lowercased())
let filterByDescription = article.body!.lowercased().range(of: (text.lowercased()))
if ((filterByName != nil) || filterByDescription != nil){
return true
}
return false
}

Filter Array in Swift based on multiple properties

struct User{
var firstName:String
var lastName:String
var city:String
var email:String
}
var users = [User]
I am trying to filter users as someone is typing in the textfield. Its like Search for the email. It should show all the matching results but shouldn’t duplicate the same user. I am able to filter the array based on one property such as name but not sure how to filter the array based on all the properties.
I’ve implemented the UITextField delegate and have this code for filtering.
let filteredArray = users.filter({ (user) -> Bool in
return user.firstName.lowercased().contains(“John”)
})
let filteredArray = users.filter({ $0.firstName.lowercased().contains("firstName") || $0.lastName.lowercased().contains("lastName") || ... })
You can set multiple conditions and combine them together with OR (||) or AND (&&)- its a simple boolean, you can think of it as it was in an if statement-
if user.firstName.lowercased().contains("john") || user.lastName.lowerCased().contains("lastname") { return true }
else { return false }
so in your code it will be like
let filteredArray = users.filter { (user) -> Bool in
return user.firstName.lowercased().contains("john") || user.lastName.lowercased().contains("lastname") }
Since you'll probably need to search multiple things, I would recommend you make a Searchable protocol, and make aggregate types searchable by virtue of checking if any of their constituents match.
import Foundation
struct User {
let firstName: String
let lastName: String
let city: String
let email: String
let age: Int // an example of a non-String type.
}
protocol Searchable {
func matches(query: String) -> Bool
}
extension String: Searchable {
func matches(query: String) -> Bool {
// Implement any kind of searching algorithm here. Could be as smart as fuzzy seraching
// or as basic as this case-insenitive simple substring search
return self.lowercased().contains(query)
}
}
extension Int: Searchable {
func matches(query: String) -> Bool {
return String(self).matches(query: query)
}
}
extension User: Searchable {
func matches(query: String) -> Bool {
let constituents: [Searchable] = [firstName, lastName, city, email, age]
return constituents.contains(where: { $0.matches(query: query) })
}
}

Swift 1.2 Filter an Array of Structs by keyword

I need some help filtering an array of Structs.
This is what I am doing currently, it filters the array but not correctly.
For example lets say I search for an item in the array with "Mid" I have one item that should be shown however the item shown starts with "Bad".
var array = breweries.filter() { $0.name?.lowercaseString.rangeOfString(searchController.searchBar.text.lowercaseString) != nil }
results = array
here is my Struct
struct Breweries {
let name: String?
let breweryId: String?
let distance: Double?
let largeIconURL: String?
let streetAddress: String?
let locality: String?
let region: String?
let phone: String?
let website: String?
init(brewDictionary: [String: AnyObject]) {
name = brewDictionary["brewery"]?["name"] as? String
breweryId = brewDictionary["breweryId"] as? String
distance = brewDictionary["distance"] as? Double
largeIconURL = brewDictionary["brewery"]?["images"]??.objectForKey("large") as? String
streetAddress = brewDictionary["streetAddress"] as? String
locality = brewDictionary["locality"] as? String
region = brewDictionary["region"] as? String
phone = brewDictionary["phone"] as? String
website = brewDictionary["website"] as? String
}
}
Please point in the right direction!
Note: I am using Swift 1.2
Update:
I thought a video would be of help to better explain what I am trying to do.
Demo Of issue
What I want is to find the filter the array so only the item with a similar name is shown.
Update 2: As it turns out I forgot to handle the case when my UISearchController was active.
Assuming your Struct name is Breweries and it has a name property, try this:
let array = breweries.filter() {
($0.name!.lowercaseString as NSString).containsString(searchController.searchBar.text.lowercaseString)
}
Your usage of filter is correct, but your closure seem to be complicated with no clear goal. I suggest you to write an extension (or possibly use what I am using):
extension String {
func contains(search: String, ignoreCase: Bool = false, ignoreDiacritic: Bool = false) -> Bool {
var options = NSStringCompareOptions.allZeros
if ignoreCase { options |= NSStringCompareOptions.CaseInsensitiveSearch }
if ignoreDiacritic { options |= NSStringCompareOptions.DiacriticInsensitiveSearch }
return self.rangeOfString(search, options: options) != nil
}
}
This way you can use closure like this to search:
breweries.filter() {
$0.name?.contains("x") // Precise search
$0.name?.contains("x", ignoreCase: true, ignoreDiacritics: true) // Ignores diacritics and lower / upper case
}
of course, you can use | or & to search for multiple parameters
breweries.filter() {
$0.name?.contains("x") || $0.streetAddress?.contains("x")
}
Hope it helps!
Here is an example from an investing app with struct:
import Foundation
public struct SNStock {
public let ticker:NSString
public let name:NSString
init(ticker:NSString, name:NSString) {
self.ticker = ticker
self.name = name
}
}
Search on Main Thread:
public func searchStocksByKeyword(keyword:String) -> [SNStock] {
let lowercaseKeyword = keyword.lowercaseString
var searchResults:[SNStock] = []
searchResults = stocks.filter({ (stock:SNStock) -> Bool in
return stock.ticker.lowercaseString.hasPrefix(lowercaseKeyword)
})
if (searchResults.count == 0) {
searchResults = stocks.filter({ (stock:SNStock) -> Bool in
return stock.name.lowercaseString.hasPrefix(lowercaseKeyword)
})
}
searchResults.sortInPlace {
($0.ticker as String) < ($1.ticker as String)
}
return searchResults;
}
Search on Background Thread:
public func searchStocksByKeyword(keyword:String, completion:(stocks:[SNStock])->()) {
let qualityOfServiceClass = QOS_CLASS_USER_INTERACTIVE
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
let stocks:[SNStock] = self.searchStocksByKeyword(keyword)
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion(stocks: stocks)
})
})
}

Resources