Using Array.map to seach in array of dictionaries - arrays

I'm trying to search for item inside of array of dictionaries and just return the match if there is any. Here is my code:
let book = self.listOfBooks.map({ (Books) -> String in
var bookName = String()
if searchText == Books?. name{
airportName = (Books?.author)!
return airportName
}
return // Error: Non-void function should return a value
})
But my problem is the .map is expecting return for each item in the array of self.listOfBooks. My question to you guys is how can just return the dictionary with only the matching the if ?
I'll really appreciate your help.

What you're looking for is flatMap not map.
let book = self.listOfBooks.flatMap({ (Books) -> String? in
var bookName = String()
if searchText == Books?.name{
airportName = Books?.author
return airportName
}
return nil
})
Shorter version
let airportNames = self.listOfBooks.flatMap { ($0?.name == searchText) ? $0?.author : nil }
Edit: If you want whole object not only author then you need to use filter for that.
let airportNames = self.listOfBooks.filter { $0?.name == searchText }

Related

How to search through struct data using UISearchBar swift 4.2

I have struct datatype which is use for decoding data
struct OtherCountry : Decodable {
let name : String
let dial_code : String
let code : String
}
struct FrequentCountry:Decodable{
let name : String
let dial_code : String
let code : String
}
I want to search based on name & code and this is stored in array of type struct
var OtherDataCountry = [OtherCountry]()
var FrequentDataCountry = [FrequentCountry]()
I have also implemented search function that looks something like this
func searchBar(searchText: String) {
searchCountry1 = OtherDataCountry.filter({ (OtherCountry) -> Bool in
return OtherCountry.name.range(of: searchText , options:[.caseInsensitive]) != nil
searchActive = !searchCountry1.isEmpty
self.mTableView.reloadData()
}
)}
}
can anyone help me to convert that struct data to String array , that would be helpful because i can search through array and use that result to show entire data .
Thank You for Help !
Please try below code
func searchBar(searchText: String) {
searchCountry1 = OtherDataCountry.filter {
$0.name.range(of: searchText , options:[.caseInsensitive]) != nil
}.map {$0.name}
searchActive = !searchCountry1.isEmpty
self.mTableView.reloadData()
}
)
}
searchCountry1 will be an array after map containing the name value

How can I merge 2 dictionaries into one array?

My JSON data look like this image below. Now I wanna merge the value of Shop Type and Promotion into one to use as collection view data. How can I do that?
I just filter the response data from the server like this:
var dataBanDau: [SDFilterModel] = []
var quickData: [SDFilterModel] = []
let filters: [SDFilterModel] = data
self.filterEntries = filters
//let nsarray = NSArray(array: self.filterEntries! , copyItems: true)
// self.filterEntriesStoreConstant = nsarray as! Array
self.dataBanDau = filters
for i in 0..<self.dataBanDau.count {
if self.dataBanDau[i].search_key.count == 0 {
self.quickData.append(self.dataBanDau[i])
}
}
self.quickData = self.quickData.filter {
$0.type != "range"
}
DispatchQueue.main.async {
//Note: Reload TableView
self.quickFilterCollection.reloadData()
completed(true)
}
}
the class SDFilterModel:
class SDFilterModel: DSBaseModel {
var name = String()
var type = String()
var is_expanded = Int()
var search_key = String()
var filterEntries : [SDFilterModel]?
override func copy(with zone: NSZone? = nil) -> Any {
// This is the reason why `init(_ model: GameModel)`
// must be required, because `GameModel` is not `final`.
let copy = SDFilterModel(dict: self.dictionary)
if let arrAttribute = NSArray(array: self.value , copyItems: true) as? [AttributeValueModel] {
copy.value = arrAttribute
}
return copy
}
override init(dict: Dictionary<String, Any>) {
super.init(dict: dict);
value = self.valueParse()
name = dict.getString(forKey: "name")
type = dict.getString(forKey: "type")
search_key = dict.getString(forKey: "search_key")
is_expanded = dict.getInt(forKey: "is_expanded")!
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
var value: [AttributeValueModel] = [];
func valueParse()-> [AttributeValueModel] {
guard let childs = (self.dictionary["value"]) as? [Dictionary<String, AnyObject>]
else { return [] }
var output: [AttributeValueModel] = [];
for aDict in childs {
let item = AttributeValueModel(dict:aDict);
// if type == .Range && item.option_id == "0" {
// item.setRangeOptionID(aValue: item.option_name!)
// }
//
output.append(item);
}
return output;
}
Let be Assume you have let myArray = [1,2,3,4,5,6,7,8]
Now you wanted to square of each and every element in the array,
With for loop you do like this
for item in myArray {
print(item * item)
}
Now assume item = $0
With for map you jus do
myArray.map({ $0 * $0 })
Both will gave same output.
map : Use to do same operation on every element of array.
flatmap : It is used to flattern the array of array.
let myArr = [[1,2,3],[4,5,6,7]]
and you want o/p as [1,2,3,4,5,6,7]
So can get above output with myArr.flatMap({$0})
Now back to your question.
let reqArray = myModel.data.map({ $0.value }).flatMap({ $0 })
First, map gaves you array-of-array of key value but you need a single array, so for that you need to use flatmap.
You can take ref : https://medium.com/#Dougly/higher-order-functions-in-swift-sorted-map-filter-reduce-dff60b5b6adf
Create the models like this
struct Option {
let name: String
let searchKey: String
let id: String
}
struct Model {
let type: String
let name: String
let isExpanded: Bool
let value: [Option]
}
You should get the options array values and join all the arrays
let models:[Model] = //...
let collectionViewArray = models.map { $0.value }.reduce([Option](), +)
Using for loop
var collectionViewArray = [Option]()
for model in models {
collectionViewArray.append(contentsOf: model.value)
}

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
}

Cannot invoke index with an argument list of type '(of: Any)'

I am making a news app where you can select topics that you want to see. The problem I am having is where you deselect the topic. All of the selected topics are added to CoreData in an Entity called ArticleSource under the Attribute of source. The error occurs when I try to locate the topic in the array called Results using the string title. As I dont know the position of the topic in the array I try to locate it using index(of: ) method which produces the error: Cannot invoke index with an argument list of type '(of: Any)'
Any help appreciated.
do {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "ArticleSource")
request.returnsObjectsAsFaults = false
var results = try context.fetch(request)
if results.count > 0 {
for result in results as! [NSManagedObject] {
if let source = result.value(forKey: "source") as? String {
if source == title {
print("it matches")
if let index = results.index(of: title) {
results.remove(at: index)
}
}
print("results = \(results)")
}
}
}
} catch {
print("error")
}
do {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
try context.save()
print("SAVED")
} catch {
// error
}
There is no need to loop through results to get the index. You can try this.
var results = context.fetch(request)
if let index = results.index(where: { (result) -> Bool in
result.value(forKey: "source") == title
})
{
results.remove(at: index)
}
A likely cause of Cannot invoke index with an argument list of type '(of: X)
is because the type X does not conform to Equatable
In arr.index(of: <Element>), Element should conform to Equatable, and type X does not conform to Equatable:
For an array of [X], use arr.index(where:)
Update your code as:
if let index = results.index(where: { $0 as? String == title }) {
print(index)
}

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