Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filter array of objects with multiple criteria and types in Swift

Tags:

swift

filter

I am trying to do some complex filtering in my app and I am to a point where I don't know what to do next. My data consistes of an array of dictionaries where the values in each of the dictionaries can be String, Int or [String].

let person1: [String : Any] = ["first_name" : "John",
                               "last_name" : "Smith",
                               "age" : 21,
                               "skills" : ["C#", "Java", "Swift"]]
let person2: [String : Any] = ["first_name" : "Kim",
                               "last_name" : "Smith",
                               "age" : 28,
                               "skills" : ["Java", "Swift"]]
let person3: [String : Any] = ["first_name" : "Kate",
                               "last_name" : "Bell",
                               "age" : 24,
                               "skills" : ["C#"]]


var people = [person1, person2, person3]

I let the user choose how to filter this data and create a dictionary of filter criteria. This dictionary can have any number of keys and values.

let filters: [String : [Any]] = ["age" : [28, 24],
                                 "skills" : ["Java", "Swift"]]

In this example I want to show persons who are age 28 or 24 and have a skills of Java or Swift, which would be person2

Here is what I have so far but it only works with Int values:

for (key, values) in filters {
    var filteredItems = people.filter {
        var match = false
        for filterValue in values {
            if $0[key] as! Int == filterValue as! Int {
                match = true
                break
            }
            else {
                match = false
            }
        }
        return match
    }
    people = filteredItems
}
like image 409
Chris Avatar asked Oct 20 '25 14:10

Chris


1 Answers

Here's how I would do this:

struct Person {
    let firstName: String
    let lastName: String
    let age: Int
    let skills: [String]
    
    enum Filter {
        enum FilterType<T: Hashable> {
            case one(of: [T])
            case all(of: [T])
            
            // Match against a property that's a single value
            func matches(_ value: T) -> Bool {
                switch self {
                    case .one(let filterValues): return filterValues.contains(value)
                    case .all(let filterValues): return filterValues.count == 1 && filterValues[0] == value
                }
            }
            
            // Match against a property that's a list of values
            func matches(_ values: [T]) -> Bool {
                switch self {
                    case .one(let filterValues): return !Set(filterValues).intersection(values).isEmpty
                    case .all(let filterValues): return Set(filterValues).isSuperset(of: values)
                }
            }
        }
        
        case age(is: FilterType<Int>)
        case skills(is: FilterType<String>)
        
        func matches(_ p: Person) -> Bool {
            switch self {
                case .age(let filterValues): return filterValues.matches(p.age)
                case .skills(let filterValues): return filterValues.matches(p.skills)
            }
        }
    }
}

extension Array where Element == Person.Filter {
    func atLeastOneMatch(_ p: Person) -> Bool {
        self.contains(where: { $0.matches(p) })
    }
    
    func matchesAll(_ p: Person) -> Bool {
        self.allSatisfy { $0.matches(p) }
    }
}

let people = [
    Person(
        firstName: "John",
        lastName : "Smith",
        age: 21,
        skills: ["C#", "Java", "Swift"]
    ),
    Person(
        firstName: "Kim",
        lastName : "Smith",
        age: 28,
        skills: ["Java", "Swift"]
    ),
    Person(
        firstName: "Kate",
        lastName: "Bell",
        age: 24,
        skills: ["C#"]
    ),
]


let filters: [Person.Filter] = [
    .age(is: .one(of: [28, 24])),
    .skills(is: .one(of: ["Java", "Swift"])),
]

let peopleWhoMatchAllFilters = people.filter(filters.matchesAll)
print(peopleWhoMatchAllFilters)

let peopleWhoMatchAtLeastOneFilter = people.filter(filters.atLeastOneMatch)
print(peopleWhoMatchAtLeastOneFilter)

I've extended the filtering capability to be able to specify wether all values of a filter should be matched (e.g. a person must know Java AND Swift AND C#) or at least one (e.g. a person must know AT LEAST Java OR Swift OR C#)

like image 131
Alexander Avatar answered Oct 23 '25 07:10

Alexander



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!