Filter Custom Array time optimization - arrays

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
}

Related

How do I concisely update a highly nested object model using swift?

I am writing a function which will dig into the deepest level of a object hierarchy and update a particular value. For reference, this is how messy a regular iteration turned out to be:
func selectedStateUpdated(data: MyObject, tabId: String, rowId: String){
for currIndex in 0..<self.model.data[0].data[0].filters.count{
if(self.model.data[0].data[0].filters[currIndex].headerName == data.headerName){
for tabIdx in 0..<model.data[0].data[0].filters[currIndex].data.count {
if(self.model.data[0].data[0].filters[currIndex].data[tabIdx].id == tabId){
for rowItemIdx in 0..<self.model.data[0].data[0].filters[currIndex].data[tabIdx].data.count {
if(self.model.data[0].data[0].filters[currIndex].data[tabIdx].data[rowItemIdx].id == rowId){
self.model.data[0].data[0].filters[currIndex].data[tabIdx].data[rowItemIdx].isSelected.toggle()
}
}
}
}
}
}
}
What is the best way to dig this deep into my data model and update a particular attribute? I am trying to update the model object which resides in the class of this function as a global object. Given that iterable objects in my object model are immutable in for-loops, is there a better way of approaching this while making sure I iterated into the proper
At its core, this is a data structure problem. You're doing a linear search through all your data to find something with a specific headerName, tabId, rowId. It's complicated, messy and slow (O(headerCount * tabCount * rowCount)!)
If dictionaries were used, this could just be:
self.model.data[0].data[0]
.filters[headerName]
.data[tabId]
.data[rowId]
.isSelected
.toggle()
If the arrays must stay, then you can clean this up a lot by extracting common parts to named local variables. Inverting the if statements (in this case, into guard statements`) also saves a ton on nesting:
func selectedStateUpdated(headerName: String, tabId: String, rowId: String){
let data = self.model.data[0].data[0] // TODO: name me better
for currIndex in 0..<data.filters.count {
let filter = data.filters[currIndex]
guard filter.headerName == headerName else { return }
for tabIdx in 0..<filter.data.count {
let tab = filter.data[tabIdx]
guard tab.id == tabId else { continue }
for rowItemIdx in 0..< tab.data.count {
let rowItem = tab.data[rowItemIdx]
guard rowItem.id == rowId else { continue }
self.model.data[0].data[0]
.filters[currIndex]
.data[tabIdx]
.data[rowItemIdx]
.isSelected
.toggle()
}
}
}
}
It could be further improved by using zip to get indices and items in one shot:
func selectedStateUpdated(headerName: String, tabId: String, rowId: String){
let data = self.model.data[0].data[0] // TODO: name me better
for (currIndex, filter) in zip(data.filters.indices, data.filters) {
guard filter.headerName == headerName else { return }
for (tabIdx, tab) in zip(filter.data.indices, filter.data) {
guard tab.id == tabId else { continue }
for (rowItemIdx, rowItem) in zip(tab.data.indices, tab.data) {
guard rowItem.id == rowId else { continue }
self.model.data[0].data[0]
.filters[currIndex]
.data[tabIdx]
.data[rowItemIdx]
.isSelected
.toggle()
}
}
}
}
Basically what you are doing when you say ".id == tabId" is a filter function. This code was quite messy. But i think i managed to figure it out:
func selectedStateUpdated(data: [MyObject], tabId: String, rowId: String) {
let filteredResult = self.model.data[0].data[0].filters
.filter({$0.headerName == data.headerName &&
$0.data.filter({$0.id == tabId}) &&
$0.data.filter({$0.id == tabId &&
$0.data.filter({$0.id == rowId})
})
})
if filteredResult.count > 0 {
filteredResult.first!.isSelected.toggle()
}
}

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 nested arrays with objects

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
}

CloudKit nil optional error

I am trying to load, then modify and resave an array.
Here is code, modify func is the top one:
func modifyUserGroupsRequestee(){
print("step2")
acceptedUsersArray.append(groupNameLbl.text!)
//error
userGroupRecordToUpdate.setObject(acceptedUsersArray as CKRecordValue?, forKey: "userGroups")
database.save(recordToUpdate) { (savedRecord, error) in
if error != nil{
print(error.debugDescription)
}else{
print("SAVED RECORD")
}
}
}
func resaveUserGroups(){
print(addedUser)
print("step1")
// add group to requestees user groups
let pred = NSPredicate(format: "username = %#", "\(addedUser)")
let query = CKQuery(recordType: "PersonalUser", predicate: pred)
let operation = CKQueryOperation(query: query)
//operation.resultsLimit = CKQueryOperationMaximumResults
operation.recordFetchedBlock = { (record: CKRecord!) in
if record != nil{
self.userGroupRecordToUpdate = record
// self.acceptedUsersArray = (record.object(forKey: "userGroups") as! Array)
print("usergroup names:: \(self.acceptedUsersArray)")
if let acceptedUserArrays = record.object(forKey: "userGroups") as? [String] {
// self.feedTableView.reloadData()
self.acceptedUsersArray = acceptedUserArrays
print("looks like we r going forward")
self.modifyUserGroupsRequestee()
// }
//self.feedTableView.reloadData()
print(groupNames.count)
print(self.acceptedUsersArray)
}
}
database.add(operation)
//self.tableView.reloadData()
// print(leaderboardInfo.count)
}
}
The function prints step1 but never gets to step2. In the bottom function, I have an if let statement I tried to create to solve my nil issue (I commented my previous code above that line- self.acceptedUsersArray... Anyway, I believe I am implementing the if let statement incorrectly, because no data is loaded, even though there is data in cloud kit.
And I do have my personal user cloudKit records set up, here's a pic:
You should try to keep your code always indented consistently.
(In Xcode editor, Cmd+A (Select All), then Ctrl+I (Re-Indent).)
With confusing comments removed, your resaveUserGroups shows as:
func resaveUserGroups() {
print(addedUser)
print("step1")
// add group to requestees user groups
let pred = NSPredicate(format: "username = %#", "\(addedUser)")
let query = CKQuery(recordType: "PersonalUser", predicate: pred)
let operation = CKQueryOperation(query: query)
operation.recordFetchedBlock = { (record: CKRecord!) in
if record != nil {
self.userGroupRecordToUpdate = record
print("usergroup names:: \(self.acceptedUsersArray)")
if let acceptedUserArrays = record.object(forKey: "userGroups") as? [String] {
self.acceptedUsersArray = acceptedUserArrays
print("looks like we r going forward")
self.modifyUserGroupsRequestee()
print(groupNames.count)
print(self.acceptedUsersArray)
}
}
database.add(operation)
}
}
Omitting some parts to clarify:
func resaveUserGroups() {
//...
operation.recordFetchedBlock = { (record: CKRecord!) in
if record != nil {
//...
}
database.add(operation)
}
}
The line database.add(operation) exists inside the recordFetchedBlock.
You may need to fix some more parts (that's another story), but at least, you need to move the line out of the closure to execute the operation you have created:
func resaveUserGroups() {
//...
operation.recordFetchedBlock = { (record: CKRecord!) in
if record != nil {
//...
}
//database.add(operation)
} //↓
database.add(operation)
}
I just solved it. Apparently there always needs to be some kind of value inside the array even if it was just created. I was trying to query an array that existed in the recordType, but not yet under the specific record.

Check If Swift Dictionary Contains No Values?

So I'm making a to-do list app, and I want the user to be notified when all of the shopping items have been deleted. I have a dictionary that contains the String:store as a key and the [String]:items as the values. Is there a fast way to check if all of the items' arrays are empty?
There's the easy way:
dicts.values.flatten().isEmpty
But that will walk through all the lists without any shortcuts. Usually that's really not a problem. But if you want to bail out when you find a non-empty one:
func isEmptyLists(dict: [String: [String]]) -> Bool {
for list in dicts.values {
if !list.isEmpty { return false }
}
return true
}
Of course you can make this much more generic to get short-cutting and convenience:
extension SequenceType {
func allPass(passPredicate: (Generator.Element) -> Bool) -> Bool {
for x in self {
if !passPredicate(x) { return false }
}
return true
}
}
let empty = dicts.values.allPass{ $0.isEmpty }
You can just use isEmpty
var dict: Dictionary<Int, String> = [:]
var result = dict.isEmpty
result will be true
A functional programming approach:
let allEmpty = arr.reduce(true) { $0 && $1.1.isEmpty }
If you're not a big fan of implicit closure arguments, you can of course name them:
let allEmpty = arr.reduce(true) { empty, tuple in empty && tuple.1.isEmpty }

Resources