【问题标题】:Filter array of objects with multiple criteria and types in Swift在 Swift 中过滤具有多个条件和类型的对象数组
【发布时间】:2017-04-07 20:40:23
【问题描述】:

我正在尝试在我的应用程序中进行一些复杂的过滤,但我已经到了不知道下一步该做什么的地步。我的数据由一组字典组成,其中每个字典中的值可以是StringInt[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]

我让用户选择如何过滤这些数据并创建过滤条件字典。这个字典可以有任意数量的键和值。

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

在此示例中,我想显示年龄为 age 28 或 24 并且拥有 Java 或 Swift 的 skills 的人,即 person2

这是我目前所拥有的,但它仅适用于 Int 值:

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
}

【问题讨论】:

  • 如果您不使用字典来完成对象/结构的工作,您会轻松很多
  • @Alexander 我明白,但在我的应用程序中,数据并不总是相同的,我并不总是知道人员对象中的键是什么。
  • 这仍然很容易由具有可选属性的对象/结构建模。从根本上说,您遇到的问题是异构字典的类型擦除。要解决这个问题,最简单的方法是使用具有强类型的数据结构...对象/结构。
  • @Alexander 你能给我一个例子,说明你将如何使用我的示例数据处理对象/结构吗?
  • 是的,我一会儿有空

标签: swift filter


【解决方案1】:

我会这样做:

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)

我已经扩展了过滤功能,以便能够指定过滤器的所有值是否应该匹配(例如,一个人必须知道 Java、Swift 和 C#)或至少一个(例如,一个人必须至少知道 Java 或Swift 或 C#)

【讨论】:

  • 感谢这个例子,它很好用。我的情况(我在最初的问题中没有完全解释)是 person 数据是从服务器中提取的 JSON。该数据可以在服务器端随时更改,例如,如果添加了"favorite_color" : "Blue" 会怎样?可以在不向Person 对象添加favoriteColor 值的情况下处理吗?
  • @Chris 这是一个有趣的问题。我认为您需要完全致力于动态([String: Any] shitshow)或完全静态(维护痛苦)的一切。你处于这个中间地带,这将使任何事情变得困难。
  • @Chris 您设想对数据模型进行此类更改的频率如何?也就是说,更新 Swift 模型以与服务数据模型保持同步会有多大的负担?
  • 服务器端数据实际上不会经常更改,我绝对可以在需要时更新应用程序数据模型。但是,我的应用程序还有另一个组件,用户可以在其中完全更改被过滤的数据类型,例如,他们可以从使用 People 对象切换到 Car 对象。
  • @Chris 找到了,您需要做的就是定义一个新的enum,就像我创建的Person.Filter 一样。您甚至可以将matches 函数提取到所有过滤器都符合的协议中。此外,您可以提取和重用FilterType&lt;T: Hashable&gt; 数量所有过滤器类型
猜你喜欢
  • 2021-07-05
  • 2021-12-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-13
  • 2020-03-23
相关资源
最近更新 更多