Comparing specific values in Dictionary key - swift

I have struct that I use for Dictionary key:
struct MyKey {
let name: String
let flag: Bool
}
What I want is, to change the "flag" property and still being able to find a value specified by that key. Here is complete playground example:
struct MyKey {
let name: String
let flag: Bool
}
extension MyKey: Hashable {
static func == (lhs: MyKey, rhs: MyKey) -> Bool {
return lhs.name == rhs.name
}
}
var fKey = MyKey(name: "fKey", flag: false)
var sKey = MyKey(name: "sKey", flag: true)
var dictFirst: [MyKey: String] = [fKey: "fValue",
sKey: "sValue"]
var dictSecond: [MyKey: String] = [fKey: "fValue",
sKey: "sValue"]
let changedFKey = MyKey(name: "fKey", flag: true)
print(dictFirst[changedFKey]) // Prints nil, want it to be fValue
In fact, in extension I tried to specify that I only cares about "name", but still It's not work as intended

You also have to implement hash value correctly, the generated hash takes flag into account:
extension MyKey: Hashable {
static func == (lhs: MyKey, rhs: MyKey) -> Bool {
return lhs.name == rhs.name
}
func hash(into hasher: inout Hasher) {
hasher.combine(name)
}
}
However, in principle this is bad design. If the flag does not affect the key, it shouldn't be part of the key. It would be better to index by name directly.

Related

How is possible to a property in a Set of objects conform to Hashable protocol in Swift?

I'm implementing a tag feature for an item list. I'm trying implementing a computed property that calculate the tag set in a list of item as the union of different tag set of each item like:
item1 - [tag1, tag2]
item2 - [tag1, tag3]
output > [tag1, tag2, tag3]
The problem is that the Tag class need to be hashable and an UID is given at each instance of the tag, even tag with the same description. So when I loop in all the item taglist to create the tag set of the whole list the results is wrong like:
output > [tag1, tag1, tag2, tag3]
Here's the code:
class TTDItem: Identifiable {
var id: UUID = UUID()
var itemDesc: String
var itemTags: Set<TTDTag>
init(itemDesc: String, itemTags: Set<TTDTag>) {
self.itemDesc = itemDesc
self.itemTags = itemTags
}
}
class TTDTag: Identifiable, Hashable {
var TTDTagDesc: String
var hashValue: Int {
return id.hashValue
}
init(TTDTagDesc: String){
self.TTDTagDesc = TTDTagDesc
}
static func ==(lhs: TTDTag, rhs: TTDTag) -> Bool {
return lhs.id == rhs.id
}
}
class TTDItemList {
var itemList: [TTDItem]
init(itemList: [TTDItem]) {
self.itemList = itemList
}
//(...)
// implement computed property taglist
func itemTagsList()-> Set<TTDTag> {
var tagSet = Set<TTDTag>()
for item in self.itemList {
tagSet = tagSet.union(item.itemTags)
}
return tagSet
}
}
How can I access only to the tag description in order to obtain the correct result? Thanks
This can be done using reduce and the union function
func itemTagsList()-> Set<TTDTag> {
itemList.map(\.itemTags).reduce(Set<TTDTag>()){ $0.union($1) }
}
Note that for Hashable you need to implement hash(into:) for TTDTag
func hash(into hasher: inout Hasher) {
hasher.combine(TTDTagDesc)
}
You should start property names with a lowercase letter and make them descriptive so maybe you could change TTDTagDesc to tagDescription
hashValue is deprecated (and you should have received a warning for this). You should override hash(into:) and use your TTDTagDesc property there.
Also, you should implement id to return TTDTagDesc, because that is what identifies a tag.
class TTDTag: Identifiable, Hashable {
var TTDTagDesc: String
// Note here
func hash(into hasher: inout Hasher) {
hasher.combine(TTDTagDesc)
}
// and here
var id: String { TTDTagDesc }
init(TTDTagDesc: String){
self.TTDTagDesc = TTDTagDesc
}
static func ==(lhs: TTDTag, rhs: TTDTag) -> Bool {
return lhs.id == rhs.id
}
}

Extra argument 'where' in call

I'm working on removing an item from an array in swift, and I'm not sure why I'm getting this error.
My code is:
var itemToRemove = list[indexPath.item]
selectedCasesArray.removeAll(where: { $0 == itemToRemove })
There code is in a CollectionView's didSelect function.
itemToRemove is of type CaseFormat and selectedCaseArray is of type [CaseFormat].
Why doesn't this work? Apple's documentation allows it in Swift 4.2+, and I'm on Swift 5
I was asked to show how CaseFormat is declared:
class CaseFormat {
var id : Int
var imageName : String
var isSelected : Bool
var solve : String
var testTicks : Int
init(id : Int, imageName : String, isSelected : Bool, solve : String, testTicks : Int) {
self.id = id
self.imageName = imageName
self.isSelected = isSelected
self.solve = solve
self.testTicks = testTicks
}
}
Since CaseFormat is not Equatable, you cannot use == with it.
It's a class so maybe you want to compare references directly using ===?
selectedCasesArray.removeAll(where: { $0 === itemToRemove })
If you want to really use ==, you have to implement Equatable, e.g.:
extension CaseFormat: Equatable {
public static func == (lhs: CaseFormat, rhs: CaseFormat) -> Bool {
return lhs.id == rhs.id
}
}
Of course, the exact behavior should depend on your use case.

Custom comparator for Swift

This is my code (simplified code):
struct SomeStruct {
let id: Int
let age: Int
}
extension SomeStruct: Hashable {
var hashValue: Int {
return id.hashValue * age.hashValue
}
static func ==(lhs: SomeStruct, rhs: SomeStruct) -> Bool {
return lhs.id == rhs.id && lhs.age == rhs.age
}
}
struct Calculator {
let struct1: [SomeStruct]
let struct2: [SomeStruct]
func uniqueById() {
let struct3 = Set(struct2).union(Set(struct1))
// I want to union it by property 'id' only.
// If the property 'id' is equal for both objects,
// the object in struct2 should be used (since that can have a different age property)
}
}
SomeStruct is a generated struct which I do not want to edit. I want to create a Set for SomeStruct that is based on 1 property: id. For that, I think I need a custom Comparator, just as Java has. Is there any Swifty way? This is the only thing I can come up with, but I am wondering if there is a better way:
struct SomeStructComparatorById: Hashable {
let someStruct: SomeStruct
var hashValue: Int {
return someStruct.id.hashValue
}
static func ==(lhs: SomeStructComparatorById, rhs: SomeStructComparatorById) -> Bool {
return lhs.someStruct.id == rhs.someStruct.id
}
}
First, I don't think this would work in Java. addAll() doesn't take a Comparator (nor does contains, etc.) Comparators are for sorting, not equality. Conceptually this is breaking how Set works in any language. Two items are not "equal" unless they can be swapped in all cases.
That tells us that we don't want a Set here. What you want here is uniqueness based on some key. That's a Dictionary (as Daniel discusses).
You could either just have a "id -> age" dictionary or "id -> struct-of-other-properties" dictionary as your primary data type (rather than using Array). Or you can turn your Array into a temporary Dictionary like this:
extension Dictionary {
init<S>(_ values: S, uniquelyKeyedBy keyPath: KeyPath<S.Element, Key>)
where S : Sequence, S.Element == Value {
let keys = values.map { $0[keyPath: keyPath] }
self.init(uniqueKeysWithValues: zip(keys, values))
}
}
And merge them like this:
let dict1 = Dictionary(struct1, uniquelyKeyedBy: \.id)
let dict2 = Dictionary(struct2, uniquelyKeyedBy: \.id)
let merged = dict1.merging(dict2, uniquingKeysWith: { old, new in old }).values
This leaves merged as [SomeStruct].
Note that this Dictionary(uniquelyKeyedBy:) has the same preconditions as Dictionary(uniqueKeysWithValues:). If there are duplicate keys, it's a programming error and will raise precondition failure.
You could do something like this:
var setOfIds: Set<Int> = []
var struct3 = struct2.filter { setOfIds.insert($0.id).inserted }
struct3 += struct1.filter { setOfIds.insert($0.id).inserted }
The result would be an array of SomeStruct, with all elements with unique ids.
You could define this as a custom operator :
infix operator *>
func *> (lhs: [SomeStruct], rhs: [SomeStruct]) -> [SomeStruct] {
var setOfIds: Set<Int> = []
var union = lhs.filter { setOfIds.insert($0.id).inserted }
union += rhs.filter { setOfIds.insert($0.id).inserted }
return union
}
Your code would then look like this:
func uniqueById() {
let struct3 = struct2 *> struct1
//use struct3
}
The short answer is no. Swift sets do not have any way to accept a custom comparator and if you absolutely must have a Set, then your wrapper idea is the only way to do it. I question the requirement for a set though.
Instead of using Set in your calculator, I recommend using dictionary.
You can use a Dictionary to produce an array where each item has a unique ID...
let struct3 = Dictionary(grouping: struct1 + struct2, by: { $0.id })
.compactMap { $0.value.max(by: { $0.age < $1.age })}
Or you can keep the elements in a [Int: SomeStruct] dictionary:
let keysAndValues = (struct1 + struct2).map { ($0.id, $0) }
let dictionary = Dictionary(keysAndValues, uniquingKeysWith: { lhs, rhs in
lhs.age > rhs.age ? lhs : rhs
})

Is it possible to have a same collection instance in a dictionary associated with multiple keys in swift?

I have a Set instance and want to put it into a Dictionary, and associate it with multiple keys so I can lookup/modify it in the future.
Following Python code is what I want to achieve in Swift.
s = set()
D = {}
D["a"] = s
D["b"] = s
D["a"].add("Hello")
D["a"].add("World")
print(D["b"]) # getting {"Hello", "World"} back
I tried something like following in Swift.
var s = Set<String>()
var D = Dictionary<String, Set<String>>()
D["a"] = s // copy of s is assigned
D["b"] = s // another copy of s is assigned
D["a"]!.insert("Hello")
D["a"]!.insert("World")
print(D["b"]!) // empty :(
Since collections in Swift hold value semantics, by the time I put a set into a dictionary, new instance is created. Is there any workaround? I know I could use NSMutableSet instead of Swift's Set, but I want to know how I can approach this by using collections with value semantics if possible.
Ah! Now we get to the heart of it. You just want a reference type based on stdlib rather than using the one that Foundation gives you. That's straightforward to implement, if slightly tedious. Just wrap a Set in a class. If you don't want full SetAlgebra or Collection conformance, you don't have to implement all of these methods. (And you might want some more init methods to make this more convenient, but hopefully those implementations are fairly obvious from your code needs.)
final class RefSet<Element> where Element: Hashable {
private var storage: Set<Element> = Set()
init() {}
}
extension RefSet: Equatable where Element: Equatable {
static func == (lhs: RefSet<Element>, rhs: RefSet<Element>) -> Bool {
return lhs.storage == rhs.storage
}
}
extension RefSet: SetAlgebra {
var isEmpty: Bool { return storage.isEmpty }
func contains(_ member: Element) -> Bool {
return storage.contains(member)
}
func union(_ other: RefSet<Element>) -> RefSet<Element> {
return RefSet(storage.union(other.storage))
}
func intersection(_ other: RefSet<Element>) -> RefSet<Element> {
return RefSet(storage.intersection(other.storage))
}
func symmetricDifference(_ other: RefSet<Element>) -> RefSet<Element> {
return RefSet(storage.symmetricDifference(other.storage))
}
#discardableResult
func insert(_ newMember: Element) -> (inserted: Bool, memberAfterInsert: Element) {
return storage.insert(newMember)
}
#discardableResult
func remove(_ member: Element) -> Element? {
return storage.remove(member)
}
#discardableResult
func update(with newMember: Element) -> Element? {
return storage.update(with: newMember)
}
func formUnion(_ other: RefSet<Element>) {
storage.formUnion(other.storage)
}
func formIntersection(_ other: RefSet<Element>) {
storage.formIntersection(other.storage)
}
func formSymmetricDifference(_ other: RefSet<Element>) {
storage.formSymmetricDifference(other.storage)
}
}
extension RefSet: Collection {
typealias Index = Set<Element>.Index
var startIndex: Index { return storage.startIndex }
var endIndex: Index { return storage.endIndex }
subscript(position: Index) -> Element {
return storage[position]
}
func index(after i: Index) -> Index {
return storage.index(after: i)
}
}

Case insensitive Dictionary in Swift

Given a Dictionary whose Key is of type String, is there a way to access the value in a case-insensitive manner? For example:
let dict = [
"name": "John",
"location": "Chicago"
]
Is there a way to call dict["NAME"], dict["nAmE"], etc. and stil get "John"?
A cleaner approach, 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"
Swift support multiple subscripting so you can take advantage of that to define a case-insensitve accessor:
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
This extension is limited to Dictionary whose Key is of type String (as lowercase is meaningless with other data types). However, Swift will complain about constraining a generic type to a struct. The protocol that is closest to String is StringLiteralConvertible.
Note that if you have 2 keys whose lowercase forms are identical, there's no guarantee which one you will get back:
let dict = [
"name": "John",
"NAME": "David",
]
print(dict[ci: "name"]) // no guarantee that you will get David or John.
The existing answers are fine, but the time complexity of lookups/insertions with those strategies deteriorates from O(1) to O(N) (where N is the number of objects in the dictionary).
To retain O(1) you may want to consider the following approach:
/// 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"
can use Collection's first(where:) to find first lowercased match from all keys mapped lowercased, then return the value from this result.
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:) is a much efficient way to filter or iterate over the large collection
reference:
https://developer.apple.com/documentation/swift/anybidirectionalcollection/2906322-first#
https://github.com/realm/SwiftLint/blob/master/Rules.md#first-where
This should do the job with O(1) while also not allowing to add the same string with different casing (e.g. if you first insert Def it is not replaced by DEF). It also works for Substring if necessary. Note, that this solution is more memory effective, but comes at the cost at recomputing the string transformation and hash on every lookup of a string. If you need to look-up the same value frequently it might be worth to have an implementation which caches the 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")]!)