【问题标题】:Multiple filters for the same searchBar and ignoring the oder in Swift 5同一搜索栏的多个过滤器并忽略 Swift 5 中的顺序
【发布时间】:2026-01-17 04:45:02
【问题描述】:

在我的应用程序中,我构建了一个 SearchBar,它可以搜索多个单词而忽略顺序 Search for multiple words ignoring the order in Swift 5

导入基础

class Clinic {
    var id = ""
    var name = ""
    var address = ""
    var specialty1 = ""
    var specialty2 = ""
}

在我的 textDidChange 我有

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {

let searArr = searchText.lowercased().components(separatedBy: " ")
clinicsSearch = clinics.filter { item in
         let lowName = item.name.lowercased()
         let lowSp1 = item.specialty1.lowercased()
         let lowSp2 = item.specialty2.lowercased()
         let res  = searArr.filter {
                      lowName.contains($0) ||    
                      lowSp1.contains($0) || 
                      lowSp2.contains($0) 
         }
   return !res.isEmpty
} // Credits to @Sh_Khan

使用上面的代码,我可以搜索任何键入的单词,与顺序无关,并且可以找到所有符合任何条件的条目。

我现在正在尝试从上面的内容构建一个变体......用例是

  • 假设总共有 10 个诊所(第一诊所、第二诊所、第三诊所、第四诊所等)。其中,3 家是骨科医师,诊所名称分别为第一诊所、第二诊所和第三诊所。所以,我可以开始输入 Orthopedist(过滤到 3 个名字)。我现在想要我输入的下一个词来过滤我的选择(例如,输入 First 会将 3 个已显示的诊所过滤到符合此附加条件的诊所)。在我的示例中,这会将列表减少为一个诊所。

我试过改变 ||通过 && 我认为这已经足够了,但我得到了一个意想不到的行为。当我开始输入时,它只考虑第一个字母,然后从过滤器中删除所有内容。对于上面的例子,如果我输入“F”,它会带来 First Clinic,但是,如果我输入“Fi”,它什么也不会带来。我还考虑过创建多个过滤变量,但这可能会将输入的单词数限制为我创建的变量数。

我想要的可行吗?

【问题讨论】:

    标签: arrays swift filter


    【解决方案1】:

    您可以使用递归搜索,将第一次搜索中的匹配项传递给下一次搜索,下一次搜索只搜索下一个搜索词的匹配项。这对每个搜索词继续进行,然后仅返回剩余的匹配项。

    我已经把它放到了一个可以在操场上运行的例子中,这样你就可以玩一玩,看看它是如何工作的。

    您可以(并且应该)对其进行优化,以便在没有剩余匹配项的情况下提前终止搜索,但我将把它作为练习留给您,因为它将帮助您了解递归搜索的工作原理。如果需要帮助,请在 cmets 中讨论。

    我还删除了一些不需要的临时变量(小写版本),使用递归意味着只使用一个过滤器。

    import UIKit
    
    class Clinic: CustomStringConvertible {
        var id = ""
        var name = ""
        var address = ""
        var specialty1 = ""
        var specialty2 = ""
        var description: String {
            get { return "Name: \(name) Sp1: \(specialty1) Sp2: \(specialty2)"}
        }
        init(data: [String]) {
            id = data[0]
            name = data[1]
            address = data[2]
            specialty1 = data[3]
            specialty2 = data[4]
        }
    }
    var clinics = [
        Clinic(data: ["1","First Clinic","First Street","head","feet"]),
        Clinic(data: ["2","Second Clinic","Second Street","legs","feet"]),
        Clinic(data: ["3","Third Clinic","Third Street","head","legs"])
    ]
    
    // recursive search: search remaining clinics with remaining searches
    func search(searchClinics: [Clinic], searchArr: [String]) -> [Clinic] {
        var searchArr = searchArr
        // base case - no more searches - return clinics found
        if searchArr.count == 0 {
            return searchClinics
        }
        // itterative case - search clinic with next search term and pass results to next search
        let foundClinics = searchClinics.filter { item in
            item.name.lowercased().contains(searchArr[0]) ||
            item.specialty1.lowercased().contains(searchArr[0]) ||
            item.specialty2.lowercased().contains(searchArr[0])
        }
        // remove completed search and call next search
        searchArr.remove(at: 0)
        return search(searchClinics: foundClinics, searchArr: searchArr)
    }
    
    let searchArr = "cli le".lowercased().components(separatedBy: " ")
    let foundClinics = search(searchClinics: clinics, searchArr: searchArr)
    foundClinics.map() {print($0)}
    // Outputs:
    // Name: Second Clinic Sp1: legs Sp2: feet
    // Name: Third Clinic Sp1: head Sp2: legs
    

    【讨论】:

      【解决方案2】:

      您应该预先计算小写字母。您也应该在后台执行此操作并将结果分派回主队列。

      抛开性能问题:

      您的逻辑是,如果搜索词中的任何字词在任何搜索目标中,那么您的逻辑是正确的。

      您想将其更改为如果搜索词中的 ALL 单词在任何搜索目标中,则返回 true。

      func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
      
        let terms = searchText.lowercased().components(separatedBy: " ")
        clinicsSearch = clinics.filter { item in
          let lowName = item.name.lowercased()
          let lowSp1 = item.specialty1.lowercased()
          let lowSp2 = item.specialty2.lowercased()
      
          let targetsContainTerm: [Bool] = searArr.map {
            lowName.contains($0) ||
            lowSp1.contains($0) ||
            lowSp2.contains($0)
          }
          return targetsContainTerm.allSatisfy { $0 = true}
      } // Credits to @Sh_Khan
      

      【讨论】: