Accessing Swift set elements by their hash value
Preamble:
- Since there are 64 squares an array of optional pieces is probably best
- In general you use a dictionary with an Int key as a sparse array (see example below)
- You should not use hashValue for location alone since it should take into account all properties (I changed to location in example below) - though I get that in this case except transiently no 2 pieces can have the same location and therefore it is probably OK
How I might code it:
struct Piece {
var row: Int
var column: Int
let player: Player
let type: PieceType
var location: Int {
return 8 * row + column
}
}
struct Board {
var pieces = [Int : Piece]()
init(pieces: Piece...) {
pieces.forEach {
self.pieces[$0.location] = $0
}
}
}
let whiteKing = Piece(row: 0, column: 4, player: .White, type: .King)
let blackKing = Piece(row: 7, column: 3, player: .Black, type: .King)
let board = Board(pieces: whiteKing, blackKing)
board.pieces // [4: {row 0, column 4, White, King}, 59: {row 7, column 3, Black, King}]
Get position as int from Set.index in Swift
Set is unordered, try using an Array for example
How to write to an Element in a Set?
You are getting the error because columns
might be a set of struct
. So columns.first
will give you an immutable value. If you were to use a class
, you will get a mutable result from columns.first
and your code will work as expected.
Otherwise, you will have to do as explained by @Sweeper in his answer.
Getting hash values from object in swift
NSLog is probably showing you the GCController's debugDescription property whereas print and the likes will show you the description property.
Set's contains method returns different value at different time
The synthesized implementation of the Hashable
requirement uses all stored
properties of a struct
, in your case string
and number
. Your implementation
of ==
is only based on the string:
let first = SimpleStruct(string: "a", number: 2)
let second = SimpleStruct(string: "a", number: 3)
print(first == second) // true
print(first.hashValue == second.hashValue) // false
This is a violation of a requirement of the Hashable
protocol:
Two instances that are equal must feed the same values to Hasher in hash(into:), in the same order.
and causes the undefined behavior. (And since hash values are randomized
since Swift 4.2, the behavior can be different in each program run.)
What probably happens in your test is that the hash value of second
is used to determine the “bucket” of the set in which the value
would be stored. That may or may not be the same bucket in which first
is stored. – But that is an implementation detail: Undefined behavior is undefined behavior, it can cause unexpected results or even
runtime errors.
Implementing
var hashValue: Int {
return string.hashValue
}
or alternatively (starting with Swift 4.2)
func hash(into hasher: inout Hasher) {
hasher.combine(string)
}
fixes the rule violation, and therefore makes your code behave as expected.
How does Set ensure equatability in Swift?
An object that conforms to Hashable
must also be Equatable
. Set
uses ==
to test for equality, it doesn't depend only on the hashValue
.
From Apple's documentation on Hashable:
Conforming to the Hashable Protocol
To use your own custom type in a
set or as the key type of a dictionary, add Hashable conformance to
your type by providing a hashValue property. The Hashable protocol
inherits from the Equatable protocol, so you must also add an equal-to
operator (==) function for your custom type.
The documentation goes on to say:
Set and dictionary performance depends on hash values that minimize
collisions for their associated element and key types, respectively.
So, the hashValue
is just used a first test for uniqueness; if the hashValue
s match then Set
will use the more computationally expensive ==
to test for uniqueness.
Swift: Using ObjectID of class for hashable protocol results in random behaviour in set.contains method. What is wrong with the code?
Simply put, Set
relies on func hash(into hasher: inout Hasher)
and ==
. It is invalid to have an unmatched pair of these. In your case, your equality is value-based (dependant upon self.number
), whereas your hash is identity based. This isn't legal.
Your mySet.contains(secondNumber1)
line is failing because secondNumber2
might have a hash collision with number1
. Whether a collision occurs or not is undefined, because Swift uses a random seed to defend against hash-flood DDoS attacks. If a hash collision does occur, then your equality operator (==
) falsely identifies as number1
as a match for secondNumber1
Instead, what you could do is implement a wrapper struct that implements equality and hashing based on an object's identity. The object itself could have its own value-based equality and hash, for other purposes.
struct IdentityWrapper<T: AnyObject> {
let object: T
init(_ object: T) { self.object = object }
}
extension IdentityWrapper: Equatable {
static func == (lhs: IdentityWrapper, rhs: IdentityWrapper) -> Bool {
return lhs.object === rhs.object
}
}
extension IdentityWrapper: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self.object))
}
}
Using the IdentityWrapper in a set requires you to manually wrap objects before interacting with the set. It's performant (since struct don't need any array allocation), and most likely the struct is entirely inlined anyway, but it can be a little annoying. Optionally, you could implement a struct IdentitySet<T>
which just wraps a Set<IdentityWrapper<T>>
, which tucks away the wrapping code.
class MyClass: Hashable {
var number: Int
init(_ number: Int) {
self.number = number
}
// Value-based equality
static func == (lhs: MyClass, rhs: MyClass) -> Bool {
return lhs.number == rhs.number
}
// Value-based hashing
func hash(into hasher: inout Hasher) {
hasher.combine(self.number)
}
}
var mySet: Set<IdentityWrapper<MyClass>> = []
let number1 = MyClass(1)
let secondNumber1 = MyClass(1)
number1 == secondNumber1 // true: integer values are equal, so are the wrapping classes
number1 === secondNumber1 // false: two different instances
mySet.insert(IdentityWrapper(number1))
print(mySet.contains(IdentityWrapper(number1))) // true
print(mySet.contains(IdentityWrapper(secondNumber1))) // false
Getting set of objects from NSSet using the HASH
if you want the object in an array just call -allObjects, if you want all hashes, then you will have to iterate through them, because they are longs and can't be stored in an NSArray directly.
I had the idea to make a mock object that overrides its own hash, then you could search through an array for the index of this object that is pretending to be your object.
@interface MockHasher : NSObject{
NSUInteger mockHash;
}
@property(assign,nonatomic,getter = hash,setter = setHash:)NSUInteger mockHash;
@end
@implementation MockHasher
@synthesize mockHash;
-(BOOL)isEqual:(id)object{return YES;}
-(BOOL)isEqualTo:(id)object{return YES;}
@end
example:
NSSet * myset = [NSSet setWithObject:@(1)];
MockHasher * mockObject = [[MockHasher new] autorelease];
mockObject.hash = @(1).hash;
NSArray * allObjects = [myset allObjects];
NSUInteger i = [allObjects indexOfObject:mockObject];
id result = [allObjects objectAtIndex:i];
NSLog(@"result = %@",result);
It is fragile, because it is depending on the array asking the object passed in for isEqual:
rather than asking the iterated object... I don't know how reliable this is... but it worked in my test.
Related Topics
Swift: Copy Information Selected by User in Abpersonviewcontroller to Dictionary
How to Check If Airpods Are Connected to Iphone
How to Make JSON Data Persistent for Offline Use (Swift 4)
Shared Cookies with Wkprocesspool for Wkwebview in Swift
How to Get a Double Value Up to 2 Decimal Places
Formsheet iOS 8 Constraints Are Same as Iphones Constraints
String Comparison in Swift Is Not Transitive
Swift Extension Storage for Protocols
Captureoutput Function Isn't Called Using Setsamplebufferdelegate
How to Access Cfdictionary in Swift 3
"Use Default Container" Doesn't Show in Icloud Capabilities
Swift "Is" Operator with Type Stored in Variable
Swift Find Superview of Given Class with Generics
Adding a Custom Font to MACos App Using Swift