Reputation: 283
I have several criteria to filter array with. These criteria are optionals and stored in struct, because user can select only part of them. I have an array of models. I tried to use filter method, but you have to provide non optional criteria to it. What approach should be to get rid of optionals and add that criteria to filter method?
Filter struct with filtering options
struct Filter {
var brand: String?
var price: Int?
var consuption: Int?
}
Model class
class CarOffer {
var brand: String
var price: Int
var color: String
var consumption: Int
}
And here what I tried to do, but no luck because filter.price is optional and I don't know will it be or not. I understand that I have to remove an optional, but how to add a filter criteria in filter method depending on it's optionality? Or I am have choosen wrong approach?
let offers: [CarOffer] = […]
func applyFilter(filter: Filter) -> [CarOffer] {
let filteredOffers = offers.filter { $0.brand == filter.brand && $0.price <= filter.price && $0.consumption <= filter.consumption }
return filteredOffers
}
Upvotes: 3
Views: 6225
Reputation: 32853
You could convert the filters to closures, and add an initializer that allows easy pass of filters that we do not care about:
struct Filter {
var brand: (String) -> Bool
var price: (Int) -> Bool
var consuption: (Int) -> Bool
init(brand: @escaping (String) -> Bool = { _ in return true },
price: @escaping (Int) -> Bool = { _ in return true },
consuption: @escaping (Int) -> Bool = { _ in return true }) {
self.brand = brand
self.price = price
self.consuption = consuption
}
}
This gives the best flexibility, as from this point on you can add any kind of filtering that you want. Like adding a filer based on your original structure, optionals for fields to ignore:
init(brand: String? = nil,
price: Int? = nil,
consuption: Int? = nil) {
self.brand = { brand == nil || brand == $0 }
self.price = { price == nil || price! <= $0 }
self.consuption = { consuption == nil || consuption! <= $0 }
}
Upvotes: 1
Reputation: 63321
You would have an easier time by simplifying and breaking up your code into smaller pieces. There's no reason why a function to filter an array by some conditions, also has to be responsible for figuring out if an element meets those conditions. You've mentally trapped yourself thinking that the filter predicate has be one one long chain of &&
conditions in a closure.
struct CarOffer {
let brand: String
let price: Int
let color: String
let consumption: Int
}
struct CarFilter {
let brand: String?
let price: Int?
let consumption: Int?
func matches(car: CarOffer) -> Bool {
if let brand = self.brand, brand != car.brand { return false }
if let price = self.price, price != car.price { return false }
if let consumption = self.consumption, consumption != car.consumption { return false }
return true
}
}
extension Sequence where Element == CarOffer {
func filter(carFilter: CarFilter) -> [CarOffer] {
return self.filter(carFilter.matches)
}
}
let filter = CarFilter(brand: nil, price: nil, consumption: nil)
let offers: [CarOffer] = [] //...
let filteredOffers = offers.filter(carFilter: filter)
Upvotes: 4
Reputation: 54745
You can simply use a default value instead of filter
s Optional values. If you use the default value of the offer
, filter
will simply return true in case the optional properties were nil.
func applyFilter(filter: Filter) -> [CarOffer] {
let filteredOffers = offers.filter { $0.brand == filter.brand && $0.price <= (filter.price ?? $0.price) && $0.consumption <= (filter.consumption ?? $0.consumption) }
return filteredOffers
}
Upvotes: 3