Swift中不区分大小写的词典

时间:2015-10-17 02:58:56

标签: swift swift2

如果Dictionary的{​​{1}}类型为Key,是否有办法以不区分大小写的方式访问该值?例如:

String

有没有办法打电话给let dict = [ "name": "John", "location": "Chicago" ] 等,并且获得dict["NAME"], dict["nAmE"]

5 个答案:

答案 0 :(得分:6)

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

"John"

此扩展程序仅限于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(因为小写对其他数据类型毫无意义)。但是,Swift会抱怨将泛型类型限制为String。最接近struct的协议是String

请注意,如果您有两个小写形式相同的键,则无法保证您将获得哪一个:

StringLiteralConvertible

答案 1 :(得分:4)

更清洁的方法,快速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"

答案 2 :(得分:1)

现有答案很好,但是采用这些策略的查找/插入的时间复杂度从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"

答案 3 :(得分:0)

可以使用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:)是过滤或迭代大型集合的一种非常有效的方法

参考:

答案 4 :(得分:0)

这应该在使用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")]!)