【问题标题】:Case insensitive Dictionary in SwiftSwift 中不区分大小写的字典
【发布时间】:2015-10-17 02:58:55
【问题描述】:

给定一个Dictionary,其Key 的类型为String,有没有办法以不区分大小写的方式访问该值?例如:

let dict = [
    "name": "John",
    "location": "Chicago"
]

有没有办法打电话dict["NAME"], dict["nAmE"]等,仍然得到"John"

【问题讨论】:

    标签: swift swift2


    【解决方案1】:

    更简洁的方法,swift 4:

    extension Dictionary where Key == String {
    
        subscript(caseInsensitive key: Key) -> Value? {
            get {
                if let k = keys.first(where: { $0.caseInsensitiveCompare(key) == .orderedSame }) {
                    return self[k]
                }
                return nil
            }
            set {
                if let k = keys.first(where: { $0.caseInsensitiveCompare(key) == .orderedSame }) {
                    self[k] = newValue
                } else {
                    self[key] = newValue
                }
            }
        }
    
    }
    
    // Usage:
    var dict = ["name": "John"]
    dict[caseInsensitive: "NAME"] = "David" // overwrites "name" value
    print(dict[caseInsensitive: "name"]!) // outputs "David"
    

    【讨论】:

    • 很好的答案,这增加了字典的灵活性而无需更改任何内容
    • 这是一个很好的答案。值得注意的是,您可以以不区分大小写的方式添加键,因此您可以创建 var dict = ["name": "John", "NAME": "David"] 并且不区分大小写的查找结果将变得不可预测。
    • 复杂度也从 O(1) 上升到 O(n)。那么也可以使用键值数组。下面的解决方案可以更好地解决这个问题。
    【解决方案2】:

    Swift 支持多个下标,因此您可以利用它来定义不区分大小写的访问器:

    extension Dictionary where Key : StringLiteralConvertible {
        subscript(ci key : Key) -> Value? {
            get {
                let searchKey = String(key).lowercaseString
                for k in self.keys {
                    let lowerK = String(k).lowercaseString
                    if searchKey == lowerK {
                        return self[k]
                    }
                }
                return nil
            }
        }
    }
    
    // Usage:
    let dict = [
        "name": "John",
        "location": "Chicago",
    ]
    
    print(dict[ci: "NAME"])      // John
    print(dict[ci: "lOcAtIoN"])  // Chicago
    

    此扩展仅限于Dictionary,其Key 的类型为String(因为小写对其他数据类型没有意义)。但是,Swift 会抱怨将泛型类型限制为struct。最接近String 的协议是StringLiteralConvertible

    请注意,如果您有 2 个小写形式相同的键,则无法保证您会返回哪一个:

    let dict = [
        "name": "John",
        "NAME": "David",
    ]
    
    print(dict[ci: "name"])   // no guarantee that you will get David or John.
    

    【讨论】:

    • 与现实世界中的键应该相同。你无法处理错别字,你应该改正它们。
    • +1 到@vikingosegundo 评论。你为什么需要这个?例如,为什么不将所有键都存储为小写(如果您以后关心大小写)。看起来像一个不应该这样的问题的体面解决方案:D
    • 我不得不在最近的一个项目中使用它。字典是从 JSON 转换而来的,我接受输入,返回值,无论键的大小写如何。我也无法将所有键都转换为小写,因为我必须将其发布回另一个 Web 服务。如需更多上下文,请搜索“不区分大小写的字典”,您会发现几乎所有有字典的语言的问题。
    • 在 JSON 的反序列化过程中将键标准化为小写之类的完整含义。
    【解决方案3】:

    现有的答案很好,但是使用这些策略进行查找/插入的时间复杂度从 O(1) 恶化到 O(N)(其中 N 是字典中的对象数)。

    要保留 O(1),您可能需要考虑以下方法:

    /// Wrapper around String which uses case-insensitive implementations for Hashable
    public struct CaseInsensitiveString: Hashable, LosslessStringConvertible, ExpressibleByStringLiteral {
        public typealias StringLiteralType = String
    
        private let value: String
        private let caseInsensitiveValue: String
    
        public init(stringLiteral: String) {
            self.value = stringLiteral
            self.caseInsensitiveValue = stringLiteral.lowercased()
        }
    
        public init?(_ description: String) {
            self.init(stringLiteral: description)
        }
    
        public var hashValue: Int {
            return self.caseInsensitiveValue.hashValue
        }
    
        public static func == (lhs: CaseInsensitiveString, rhs: CaseInsensitiveString) -> Bool {
            return lhs.caseInsensitiveValue == rhs.caseInsensitiveValue
        }
    
        public var description: String {
            return value
        }
    }
    
    var dict = [CaseInsensitiveString: String]()
    dict["name"] = "John"
    dict["NAME"] = "David" // overwrites "name" value
    print(dict["name"]!) // outputs "David"
    

    【讨论】:

    • 将密钥存储翻倍似乎成本很高。
    【解决方案4】:

    可以使用Collection's first(where:)从所有映射为小写的键中找到第一个小写匹配,然后从这个结果中返回值。

    extension Dictionary where Key == String {
        func valueForKeyInsensitive<T>(key: Key) -> T? {
            let foundKey = self.keys.first { $0.compare(key, options: .caseInsensitive) == .orderedSame } ?? key
            return self[foundKey] as? T
        }
    }
    

    first(where:) 是过滤或迭代大型集合的一种非常有效的方法

    参考:

    【讨论】:

      【解决方案5】:

      这应该使用 O(1) 完成这项工作,同时也不允许添加具有不同大小写的相同字符串(例如,如果您首先插入 Def 它不会被 DEF 替换)。如有必要,它也适用于 Substring。请注意,此解决方案更节省内存,但代价是在每次查找字符串时重新计算字符串转换和散列。如果您需要经常查找相同的值,则可能值得拥有一个缓存 hashValue 的实现。

      struct CaseInsensitiveString<T: StringProtocol>: Hashable, Equatable, CustomStringConvertible {
          var string: T
      
          init(_ string: T) {
              self.string = string
          }
      
          var description: String { get {
              return string.description
          }}
      
          var hashValue: Int { get {
              string.lowercased().hashValue
          } }
      
          func hash(into hasher: inout Hasher) {
              hasher.combine(hashValue)
          }
      
          static func == (lhs: Self, rhs: Self) -> Bool {
              return lhs.string.compare(rhs.string, options: .caseInsensitive) == .orderedSame
          }
      }
      
      typealias SubstringCI = CaseInsensitiveString<String>
      
      var codeMap = [SubstringCI: Int]()
      let test = "Abc Def Ghi"
      let testsub = test[test.firstIndex(of: "D")!...test.lastIndex(of: "f")!]
      codeMap[SubstringCI(String(testsub))] = 1
      print(codeMap.keys, codeMap[SubstringCI("Def")]!, codeMap[SubstringCI("def")]!)
      codeMap[SubstringCI("DEF")] = 1
      print(codeMap.keys, codeMap[SubstringCI("Def")]!, codeMap[SubstringCI("def")]!)
      

      【讨论】:

        猜你喜欢
        • 2020-02-18
        • 2018-12-30
        • 1970-01-01
        • 2017-12-01
        • 2022-11-07
        • 2012-12-09
        • 1970-01-01
        相关资源
        最近更新 更多