用于多重过滤 Swift 的布尔语句

Posted

技术标签:

【中文标题】用于多重过滤 Swift 的布尔语句【英文标题】:Boolean statement for multiple filtering Swift 【发布时间】:2019-03-21 00:48:39 【问题描述】:

我目前正在 Swift 4.2 中创建一个应用程序,我想要一个允许用户选择多个过滤器的过滤功能。

我有一个当前选择的过滤器数组,例如 ["Low", "Unread"]。我还有一组被过滤的对象。但我正在努力弄清楚如何对这个数组应用多个过滤器,特别是因为对象有子对象,而子对象又具有被过滤的属性。例如,对象数组包含 bulletin.importance.name,这是检查“低”的属性。

以下代码是一个布尔返回函数,它将获取要在公告对象数组上使用的过滤器:

return (bulletin.bulletinVersion?.issued == true) && (scopes.contains("All") || (scopes.contains((bulletin.bulletinVersion?.bulletin?.importance?.name)!) ||
        (!scopes.contains(where: $0 == "Low" || $0 == "Normal" || $0 == "High"))) && (scopes.contains(bulletin.read(i: bulletin.firstReadDate)) ||
            (!scopes.contains(where: $0 == "Unread"))) &&
            (scopes.contains(bulletin.signed(i: bulletin.signDate)) && bulletin.bulletinVersion?.bulletin?.requiresSignature == true) && scopes.contains(bulletin.favourited(i: bulletin.favourite)))

这是我当前的布尔检查尝试。我希望它是硬设置,所以如果用户选择“高”和“未读”,它只会显示与这两个过滤器匹配的对象。

在此处调用该函数,获取过滤器并根据过滤器过滤应在其中显示的所有公告的数组:

currentBulletinArray = bulletinArray.filter(bulletin -> Bool in
    let doesCategoryMatch = getScopeFilters(issued: true, scopes: scope, bulletin: bulletin, signature: true)

    if searchBarIsEmpty()
        return doesCategoryMatch
     else 
        return doesCategoryMatch && (bulletin.bulletinVersion?.bulletin?.name?.lowercased().contains(searchText.lowercased()))!
    
   )

我希望能够设置任何过滤器组合,其中返回的 .filter 谓词将仅显示与所有过滤器匹配的公告。 因此,未读 + 未签名 + 高将显示所有未读和未签名的高重要性公告。

【问题讨论】:

您可以从pastebin.com/VFi1mFyT 开始吗?您可以为bulletin 创建方法来管理已经存在的一些案例,这应该可以减轻您的长时间测试。 @Larme 这非常有用。谢谢你。我现在重新整理一下。使用辅助函数测试每个布尔值,我将如何将它们连接在一起以确保每个公告都遵守每个检查? 我注意到公告具有公告版本,而公告版本又具有其他公告。你能展示一些关于这些对象如何关联的屏幕截图吗?这些是 Core Data 对象吗? 您是否要求实现getScopeFilters 方法来替代那个庞大的布尔语句集群?或者getScopeFilters 方法是否会是该布尔语句网络的“补充”? @JacobBarnard 发布,我希望它足够详细,可以看到我在哪里进行了设计更改。 【参考方案1】:

我没有尝试将大量布尔组合塞入一个语句中,而是决定将每个过滤器放入字典中:

 public var filters:[String: Any] = ["importance":[],
                             "unread": false,
                             "unsigned": false,
                             "favourite": false]

重要性过滤器将保存一个字符串数组,例如 ["Low", "Medium", "High"]。

当点击过滤器按钮时,过滤器将被切换,如果它们是布尔值或从数组中附加/删除:

if(!importanceToAdd.isEmpty)
            filters["importance"] = importanceToAdd
        
        if(cell.filterTitleLabel.text == "Unread")
        
            filters["unread"] = true
        
        if(cell.filterTitleLabel.text == "Unsigned")
        
            filters["unsigned"] = true
        
        if(cell.filterTitleLabel.text == "Favourites")
        
            filters["favourite"] = true
        

然后,在一个单独的函数中,我检查是否设置了过滤器,彼此独立。如果是这样,请按以下每个条件过滤公告数组:

 if let importance = filters["importance"] as! [String]?
        if(importance.count != 0)
            filteredBulletins = filteredBulletins.filter(importance.contains(($0.bulletinVersion?.bulletin?.importance?.name)!))
        
    

    if let unread = filters["unread"] as! Bool?
    
        if(unread)
        
            filteredBulletins = filteredBulletins.filter($0.firstReadDate == nil)
        

    

    if let unsigned = filters["unsigned"] as! Bool?
    
        if(unsigned)
        
            filteredBulletins = filteredBulletins.filter($0.bulletinVersion?.bulletin?.requiresSignature == true && $0.signDate == nil)
        
    

    if let favourite = filters["favourite"] as! Bool?
    
        if(favourite)
        
            filteredBulletins = filteredBulletins.filter($0.favourite == true)
        
    

在字典中包含和删除过滤器确实让我的需求更加清晰。试图创建一个布尔语句的怪物将非常难以动态地匹配每个可能的过滤器组合(我什至不确定它是否可能)。

但感谢所有评论提供替代解决方案的人,您真的帮助我跳出框框思考! :)

【讨论】:

是的。它看起来比您问题中带括号的布尔语句的集合要干净得多。还要考虑您所谓的“过滤器”与 Swift 为 Collection 类型提供的 filter 方法之间的语义冲突。对于其他阅读您的代码的人来说,这可能会有些混乱。 filters dict 的键实际上只是“开”或“关”的标志。您可以考虑将filters 数组称为tagsflags 以避免语义混淆。不过没什么大不了的,因为一个是方法,另一个是对象。 @JacobBarnard 是的,我明白你的意思,我会确保放入大量 cmets 以确保不会混淆。感谢您的所有反馈!【参考方案2】:

我不确定你在问什么,但这里是。首先,您可以将代码格式化为更具可读性......我知道多个返回点被认为是不好的,但趋势似乎正在转向一点。我会按重要性顺序挑选案例并返回您可以找到的任何明确的布尔值。例如(未经测试的代码,因为我不知道 Bulletin 和 Scopes 是什么):

func foo(bulletin: Bulletin, scopes: Scopes) -> Bool 
    if bulletin.bulletinVersion?.issued == true && scopes.contains("All") 
        return true
    
    if scopes.contains(bulletin.bulletinVersion?.bulletin?.importance?.name)! 
        return true
    
    if scopes.contains(bulletin.bulletinVersion?.bulletin?.importance?.name)! 
        return true
    
    if !scopes.contains(where: $0 == "Low" || $0 == "Normal" || $0 == "High")
        && (scopes.contains(bulletin.read(i: bulletin.firstReadDate) 
        return true
    

    if !scopes.contains(where: $0 == "Unread") 
        && scopes.contains(bulletin.signed(i: bulletin.signDate)
        && bulletin.bulletinVersion?.bulletin?.requiresSignature == true
        && scopes.contains(bulletin.favourited(i: bulletin.favourite)) 
        return true
    
    return false

请注意,这些子句中的每一个都测试 true 并在适当时返回它,因此一旦找到 true 情况,函数就会返回。 tsts 的顺序可能会影响逻辑。

我会考虑创建一个协议,其方法采用公告、范围并返回真或假。

protocol Filter 
    func test(bulletin: Bulletin, scopes: Scopes) -> Bool

然后创建这个协议的实现者:

class AFilter: Filter 
    func test(bulletin: Bulletin, scopes: Scopes) -> Bool 
        if bulletin.bulletinVersion?.issued == true && scopes.contains("All") 
            return true
        
        return false
    

一旦你建立了一个你可以做的过滤器实例列表(修改以符合明确的要求):

for filter in filters 
    if !filter.test(bulletin: bulletin, scopes: scopes) 
        return false
    

return true

(奖励:注意使用这种设计模式更改逻辑是多么容易)

【讨论】:

这不会返回匹配一个过滤器和另一个过滤器的所有公告吗?所以说过滤器集是“未读”+“高”,它会返回所有未读公告和所有高公告?我会更新我的问题,使其更具体。 反转逻辑然后.....检查明确的否定并返回false。如果您通过过滤器列表,则返回 true。 更改条件顺序以便您可以使用guard let version = bulletin.bulletinVersion else return false 。它将简化一切。 另外注意可以使用return filters.allSatisfy $0.test(bulletin: bulletIn, scopes: scopes) @Sulthan:两者都是优点,也是我会做的事情。然而,我故意尽量让代码尽可能接近 OP 示例 - 避免分心。

以上是关于用于多重过滤 Swift 的布尔语句的主要内容,如果未能解决你的问题,请参考以下文章

python中循环语句

在swift中使用switch语句的布尔函数[重复]

如何在 Swift 的布尔数组中找到多个 True 语句

在 Swift UI 的布尔语句中使用状态变量

ElasticSearch 常用的查询过滤语句

ElasticSearch 常用的查询过滤语句