Swift: Hashable struct with dictionary property - swift

I have a struct in Swift that looks like this:
internal struct MapKey {
internal let id: String
internal let values: [String:String]
}
extension MapKey: Equatable {}
func ==(lhs: MapKey, rhs: MapKey) -> Bool {
return lhs.id == rhs.id && lhs.values == rhs.values
}
I now have the need to use MapKey as the key in a Swift dictionary, which requires MapKey to conform to the Hashable protocol.
What would a correct implementation of Hashable be for a struct like this one?
extension MapKey: Hashable {
var hashValue: Int {
return ??? // values does not have a hash function/property.
}
}
I've been doing some research but failed to identify what the proper way to hash a dictionary is, as I need to be able to generate a hash value for values property itself. Any help is much appreciated.

I think you need to review your data model if you have to use a whole struct as a dictionary key. Anyhow, here's one way to do it:
internal struct MapKey: Hashable {
internal let id: String
internal let values: [String:String]
var hashValue: Int {
get {
var hashString = self.id + ";"
for key in values.keys.sort() {
hashString += key + ";" + values[key]!
}
return hashString.hashValue
}
}
}
func ==(lhs: MapKey, rhs: MapKey) -> Bool {
return lhs.id == rhs.id && lhs.values == rhs.values
}
This assumes that you don't have semicolon (;) in id or in the keys and values of values. Hasable implies Equatable so you don't need to declare it conforming to Equatable again.

Since both id and values are immutable both are ok to use as basis for equals and hashValue.
However - if MapKey.id (which the name somewhat implies) uniquely identifies the MapKey (at least within the context of one dictionary)
then it is both easier and more performant to just use the MakKey.id as basis for == operator as well as hashValue
internal struct MapKey: Hashable {
internal let id: String
internal let values: [String:String]
var hashValue: Int {
get { return self.id.hashValue}
}
}
func ==(lhs: MapKey, rhs: MapKey) -> Bool {
return lhs.id == rhs.id
}

foundation data types are Hashable in Swift 4.2, you only need to let your MapKey struct to conform Hashable protocol:
struct MapKey: Hashable {
let id: String
let values: [String: String]
}
in case you want to use a class, you need conform hash(:) func like this:
class MapKey: Hashable {
static func == (lhs: MapKey, rhs: MapKey) -> Bool {
return lhs.id == rhs.id && lhs.values == rhs.values
}
let id: String = ""
let values: [String: String] = [:]
func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(values)
}
}

Related

Swift - Can I make a protocol Hashable?

I have written a simple protocol Data :
public protocol Data {
var state: [String: Any] { get set }
var objectId: String? { get set }
}
public extension Data {
var objectId: String? {
get {
return self.state["objectId"] as? String
}
set {
self.state["objectId"] = newValue
}
}
}
This way, I have created several types conforming to it:
public struct Person: Data {
public var state: [String : Any]
}
public struct Car: Data {
public var state: [String : Any]
}
// ...
Now what I want to do is to make each of these types Hashable, the problem is that I need to write this code in every type:
extension Car Hashable {
public static func == (lhs: Car, rhs: Car) -> Bool {
return lhs.objectId == rhs.objectId
}
public func hash(into hasher: inout Hasher) {
hasher.combine(self.objectId ?? "")
}
}
// ...
What I want to know is if it is possible to generically declare Data as Hashable from its objectId. As I am using a protocol, I couldn't find a way to do so.
Thank you for your help.
As #Leo Dabus mentioned in his comment, you should probably use another name for your protocol due to native Foundation.Data type existence.
Either way, using the following code you can implement Hashable protocol into your Data protocol:
public protocol Data: Hashable {
var state: [String: Any] { get set }
var objectId: String? { get set }
}
public extension Data {
var objectId: String? {
get {
return self.state["objectId"] as? String
}
set {
self.state["objectId"] = newValue
}
}
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.objectId == rhs.objectId
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.objectId ?? "")
}
}
Although this will have as a side effect that protocol Data can only be used as a generic constraint because it has Self or associated type requirements:
Meaning that you can now on use the Data protocol like this:
func myFunc<T: Data>(data: T) {
}

Swift enum protocol conformance

I want to create a Swift protocol that my different enums can conform to, so I can use the same 'type' while utilizing the enum's rawValue. Basically, the protocol should look like this:
protocol SidebarCustomFilter {
var image: UIImage { get }
var filterPredicate: NSPredicate { get }
}
An example enum that conforms to this:
enum SidebarFilterLogs : String, CaseIterable, SidebarCustomFilter{
case filterAll = "All"
case filterThisWeek = "This Week"
var image: UIImage {
return UIImage(systemName: "tray.full.fill")!
}
var filterPredicate: NSPredicate {
return NSPredicate.init(format: "TRUEPREDICATE")
}
}
Now I want to use this enum inside a struct:
struct CJSidebarFilterItem: Hashable {
private var identifier: String = ""
var sectionType: SectionType
var sidebarCustomFilter: SidebarCustomFilter?
init(logsFilter: SidebarFilterLogs) {
self.sectionType = .filters
self.sidebarCustomFilter = logsFilter
self.identifier = UUID().uuidString
}
func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
}
static func == (lhs: CJSidebarFilterItem, rhs: CJSidebarFilterItem) -> Bool {
return lhs.identifier == rhs.identifier
}
}
So far so good. However, if I try to use the 'rawValue' for the enum (of the protocol type I've described above), it gives me an error
sidebarItem.sidebarCustomFilter?.rawValue // Value of type 'SidebarCustomFilter' has no member 'rawValue'
That makes sense, since the SidebarCustomFilter is only a protocol. However, if I try to make it inherit from RawRepresentable (which should allow it to work with rawValue),
protocol SidebarCustomFilter: RawRepresentable {
var image: UIImage { get }
var filterPredicate: NSPredicate { get }
}
I get a different error:
var sidebarCustomFilter: SidebarCustomFilter? // Protocol 'SidebarCustomFilter' can only be used as a generic constraint because it has Self or associated type requirements
I believer this has something to do with RawRepresentable using an associatedtype RawValue, but I'm not sure how to resolve it.
So how do I get my protocol to work such that it enforces that only other enums are conforming to it (and hence it's valid to use 'rawValue' against it)?
Found a solution:
protocol SidebarCustomFilter {
var name: String { get }
var image: UIImage { get }
var filterPredicate: NSPredicate { get }
}
extension SidebarCustomFilter where Self: RawRepresentable {
var name: Self.RawValue {
return self.rawValue
}
}
Now I can use sidebarItem.sidebarCustomFilter?.name instead of directly asking for the rawValue, and it works!
EDIT: adding code to show how it's stored:
struct CJSidebarFilterItem: Hashable {
private var identifier: String = ""
var sectionType: SectionType
var sidebarCustomFilter: SidebarCustomFilter? // storing as generic type
init(logsFilter: SidebarFilterLogs) {
self.sectionType = .filters
self.sidebarCustomFilter = logsFilter
self.identifier = UUID().uuidString
}
func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
}
static func == (lhs: CJSidebarFilterItem, rhs: CJSidebarFilterItem) -> Bool {
return lhs.identifier == rhs.identifier
}
}
and how it's used:
let titleString = sidebarItem.sidebarCustomFilter?.name ?? ""

Swift set equatable sometimes true sometimes false

I have a struct conforming to Hashable. This model is put in a Set. Randomly when I check if the set contains the model it returns true/false. Why is this?
enum Feature: String {
case a
case b
}
struct FeatureState: Hashable {
let feature: Feature
let isEnabled: Bool
}
extension FeatureState: Equatable {
static func == (lhs: FeatureState, rhs: FeatureState) -> Bool {
lhs.feature == rhs.feature
}
}
let fs1 = FeatureState(feature: .a, isEnabled: false)
let fs2 = FeatureState(feature: .a, isEnabled: true)
featureStates.insert(fs1)
print(featureStates.contains(fs2)) // sometimes true, sometimes false
Set.contains uses hashes to check whether an element is already part of the Set or not and only uses the == operator if the hash values of two elements are the same. Because of this, you need to provide your own hash(into:) implementation to make the hash value only dependant on feature, but not isEnabled.
struct FeatureState {
let feature: Feature
let isEnabled: Bool
}
extension FeatureState: Hashable {
static func == (lhs: FeatureState, rhs: FeatureState) -> Bool {
lhs.feature == rhs.feature
}
func hash(into hasher: inout Hasher) {
hasher.combine(feature)
}
}

Is it possible to make a Heterogeneous Set in Swift?

Consider this example:
protocol Observable: Hashable {
// ...
}
struct People: Observable {
var name: String
var age: Double
var hashValue: Int {
// ...
}
static func ==(lhs: People, rhs: People) -> Bool {
// ,,,
}
}
struct Color: Observable {
var red: Double, green: Double, blue: Double
var hashValue: Int {
// ...
}
static func ==(lhs: Color, rhs: Color) -> Bool {
// ...
}
}
var observers: Set<Observable> = [] // Not allowed by the compiler
People and Color are both conform to Observable protocol which also inherit from Hashable protocol. I want to store these inside the observers set.
using 'Observable' as a concrete type conforming to protocol
'Hashable' is not supported
Is it possible to do heterogenous Set in Swift?
There is a way to make it possible. (Inspired by Apple's implementation)
Before we begin, this is what we want to build.
protocol Observer: Hashable {
associatedtype Sender: Observable
func valueDidChangeInSender(_ sender: Sender, keypath: String, newValue: Any)
}
The source of this problem is the use of Self that force the array to be Homogenous. You can see it here:
The most important change is that it stop the protocol from being usable as a type.
That makes us can't do:
var observers: [Observer] = [] // Observer is not usable as a type.
Therefore, we need another way to make it work.
We don't do
var observers: [AnyHashable] = []
Because AnyHashable will not constrain the object to conform Observer protocol. Instead, we can wrap the Observer object in the AnyObserver wrapper like this:
var observers: [AnyObserver] = []
observers.append(AnyObserver(yourObject))
This will make sure the value of AnyObserver struct conforms to Observer protocol.
According to WWDC 2015: Protocol-Oriented Programming in Swift, we can make a bridge with isEqual(_:) method so we can compare two Any. This way the object doesn't have to conform to Equatable Protocol.
protocol AnyObserverBox {
var hashValue: Int { get }
var base: Any { get }
func unbox<T: Hashable>() -> T
func isEqual(to other: AnyObserverBox) -> Bool
}
After that, we make the box that conforms to AnyObserverBox.
struct HashableBox<Base: Hashable>: AnyObserverBox {
let _base: Base
init(_ base: Base) {
_base = base
}
var base: Any {
return _base
}
var hashValue: Int {
return _base.hashValue
}
func unbox<T: Hashable>() -> T {
return (self as AnyObserverBox as! HashableBox<T>)._base
}
func isEqual(to other: AnyObserverBox) -> Bool {
return _base == other.unbox()
}
}
This box contains the actual value of the AnyObserver that we will create later.
Finally we make the AnyObserver.
struct AnyObserver {
private var box: AnyObserverBox
public var base: Any {
return box.base
}
public init<T>(_ base: T) where T: Observer {
box = HashableBox<T>(base)
}
}
extension AnyObserver: Hashable {
static func ==(lhs: AnyObserver, rhs: AnyObserver) -> Bool {
// Hey! We can do a comparison without Equatable protocol.
return lhs.box.isEqual(to: rhs.box)
}
var hashValue: Int {
return box.hashValue
}
}
With all of that in place, we can do:
var observers: [AnyObserver] = []
observers.append(AnyObserver(yourObject))
Actually, you cannot declare a Set -or even an array- of type Observable, that's because at some level Observable represents a generic protocol:
Observable -> Hashable -> Equatable:
which contains:
public static func ==(lhs: Self, rhs: Self) -> Bool
That's the reason why of the inability of using it in Heterogenous way. Furthermore, you can't declare an existential type:
var object: Observable?
// error: protocol 'Observable' can only be used as a generic constraint
// because it has Self or associated type requirements
If you are wondering what's the reason of this constraint, I assume that it is logical to compare tow People or tow Color, but not comparing People with Color.
So, what can we do?
As a workaround, you could let your set to be a set of AnyHashable structure (as #Leo mentioned in the comment):
The AnyHashable type forwards equality comparisons and hashing
operations to an underlying hashable value, hiding its specific
underlying type.
As follows:
let people = People(name: "name", age: 101)
let color = Color(red: 101, green: 101, blue: 101)
var observers: Set<AnyHashable> = []
observers.insert(people)
observers.insert(color)
for (index, element) in observers.enumerated() {
if element is People {
print("\(index): people")
}
}
that would be legal.

"Incomplete" Equatable implementation (Swift)

Is it a good idea to implement the Equatable protocol for a Swift struct in a way to compare only ID-s (which is supposed to be unique) and ignore the rest of the properties? What are drawbacks of this approach?
struct Person {
let id: String
let name: String
var surname: String
}
extension Person: Equatable {
static func ==(lhs: Person, rhs: Person) -> Bool {
return lhs.id == rhs.id
}
}