Skip to content

Commit

Permalink
Merge pull request #56 from ba-st/comparison
Browse files Browse the repository at this point in the history
Improve hash combination capabilities
  • Loading branch information
gcotelli authored Jun 10, 2020
2 parents 8341426 + 8454ba7 commit 02693d9
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 85 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ This library provides additional abstractions for Collections. See the [related

### Comparison

This library provides support to compare objects both for equality and identity. They are typically used to implement the = and hash methods. See the [related documentation.](docs/Comparison.md)
This library provides support to compare objects both for equality and identity. They are typically used to implement the `=` and `hash` methods. See the [related documentation.](docs/Comparison.md)

### Math

Expand Down
31 changes: 18 additions & 13 deletions docs/Comparison.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
# Comparison

This package includes classes to compare objects both for equality and identity. They are typically used to implement the `=` and `hash` methods.
This package includes classes to ease the comparison of objects both for equality and identity. They are typically used to implement the `=` and `hash` methods.

## `ExclusiveLogicalOr`
It builds an exclusive or between all its arguments. There are three ways of using it:
- `ofAll:` : executes a `bitXor:` iterating over the elements. It will fail if the collection is empty.
- `ofHashesOfAll::` : executes a hash and then `bitXor:` iterating over the elements. It will fail if the collection is empty.
- `collecting: aBlock ofAll: anObjectCollection` : collects the `aBlock:` of all elements and then executes `bitXor:` iterating over the results. It will fail if the collection is empty.
## Hash Combinators
Hash combinators help to implement the `hash` or `identityHash` methods by providing an easy way to combine hashes. Any object can send to itself the `equalityHashCombinator` message and then use it to calculate the combined hash value. The default equality hash combinator uses bitXor operations, but any object can override this default behavior and provide a more specific policy.

Some examples
It will apply the combinator operator to each one of the objects:
- `combineHashesOfAll:` will send the hash message to every object in the collection and then combine them.
- `combineHashOf:with:` will send the hash message to the two objects and then combine them.
- `combineAll::` expectes a collection of hashes and will combine them.
- `combine:with:` will combine two hash values

### Examples

```smalltalk
ExclusiveLogicalOr ofAll: #(2 5 6) >>> ( ( 2 bitXor: 5 ) bitXor: 6 )
ExclusiveLogicalOr ofAll: #(100 0 4 50) >>> ( ( ( 100 bitXor: 50 ) bitXor: 4 ) bitXor: 0 )
ExclusiveLogicalOr collecting: #hash ofAll: #('alpha' 'beta' 'gamma') >>> (( 'alpha' hash bitXor: 'beta' hash ) bitXor: 'gamma' hash )
ExclusiveLogicalOr ofHashesOfAll: #('alpha' 'beta' 'gamma') >>> (( 'alpha' hash bitXor: 'beta' hash ) bitXor: 'gamma' hash )
hash
^ self equalityHashCombinator combineAll: #(2 5 6)
hash
^ self equalityHashCombinator combineHashesOfAll: { alpha. beta. gamma }
```

## `StandardComparator`
It eases the implementation of comparison for equality of objects. Instances can be built in different ways:

- `StandardComparator differentiatingType`: compares for identity (`==`) or if an object `isKindOf` anotherObject.
- `StandardComparator differentiatingType`: compares for identity (`==`) or if an object `isKindOf` anotherObject.
- `StandardComparator differentiatingSending: aSelectorsCollection`: compares for identity (`==`), or if an object `isKindOf` anotherObject and all selectors are equal for both objects.
- `StandardComparator differentiatingThrough: aBlock`: compares for identity (`==`), or if an object `isKindOf` anotherObject and the block returns true when applied to both objects.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"
A BitwiseExclusiveDisjunctionHashCombinatorTest is a test class for testing the behavior of BitwiseExclusiveDisjunctionHashCombinator
"
Class {
#name : #BitwiseExclusiveDisjunctionHashCombinatorTest,
#superclass : #TestCase,
#instVars : [
'hashCombinator'
],
#category : #'Buoy-Comparison-Tests'
}

{ #category : #running }
BitwiseExclusiveDisjunctionHashCombinatorTest >> setUp [

super setUp.
hashCombinator := BitwiseExclusiveDisjunctionHashCombinator new
]

{ #category : #tests }
BitwiseExclusiveDisjunctionHashCombinatorTest >> testCannotCombineAnEmptyCollection [

self
should: [ hashCombinator combineAll: #() ]
raise: AssertionFailed
withMessageText: 'Cannot combine an empty collection of hashes'
]

{ #category : #tests }
BitwiseExclusiveDisjunctionHashCombinatorTest >> testCombineAll [

self
assert: ( hashCombinator combineAll: #(2 5 6) ) equals: ( ( 2 bitXor: 5 ) bitXor: 6 );
assert: ( hashCombinator combineAll: #(20 1) ) equals: ( 1 bitXor: 20 );
assert: ( hashCombinator combineAll: #(100 0 4 50) )
equals: ( ( ( 100 bitXor: 50 ) bitXor: 4 ) bitXor: 0 );
assert: ( hashCombinator combineAll: #(2) ) equals: 2;
assert: ( hashCombinator combineAll: #(20) ) equals: 20;
assert: ( hashCombinator combineAll: #(100) ) equals: 100
]

{ #category : #tests }
BitwiseExclusiveDisjunctionHashCombinatorTest >> testCombineHashOfWith [

self
assert: ( hashCombinator combineHashOf: 'alpha' with: 'beta' )
equals: ( 'alpha' hash bitXor: 'beta' hash )
]

{ #category : #tests }
BitwiseExclusiveDisjunctionHashCombinatorTest >> testCombineHashesOfAll [

self
assert: ( hashCombinator combineHashesOfAll: #('alpha' 'beta' 'gamma') )
equals: ( ( 'alpha' hash bitXor: 'beta' hash ) bitXor: 'gamma' hash )
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Class {
#name : #BuoyComparisonObjectExtensionsTest,
#superclass : #TestCase,
#category : #'Buoy-Comparison-Tests'
}

{ #category : #tests }
BuoyComparisonObjectExtensionsTest >> testHash [

self assert: ( ObjectUsingEqualityHashCombinator with: 1 and: 2 ) hash equals: ( 1 bitXor: 2 )
]
41 changes: 0 additions & 41 deletions source/Buoy-Comparison-Tests/ExclusiveLogicalOrTest.class.st

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Class {
#name : #ObjectUsingEqualityHashCombinator,
#superclass : #Object,
#instVars : [
'first',
'second'
],
#category : #'Buoy-Comparison-Tests'
}

{ #category : #'instance creation' }
ObjectUsingEqualityHashCombinator class >> with: anInteger and: anInteger2 [

^ self new initializeWith: anInteger and: anInteger2
]

{ #category : #comparing }
ObjectUsingEqualityHashCombinator >> hash [

^ self equalityHashCombinator combineHashOf: first with: second
]

{ #category : #initialization }
ObjectUsingEqualityHashCombinator >> initializeWith: anInteger and: anInteger2 [

first := anInteger.
second := anInteger2
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"
I'm a HashCombinator that uses bitXor: as the operation combinator.
"
Class {
#name : #BitwiseExclusiveDisjunctionHashCombinator,
#superclass : #HashCombinator,
#category : #'Buoy-Comparison'
}

{ #category : #combining }
BitwiseExclusiveDisjunctionHashCombinator >> combine: acumulatedHashValue with: hashValue [

^ acumulatedHashValue bitXor: hashValue
]
30 changes: 0 additions & 30 deletions source/Buoy-Comparison/ExclusiveLogicalOr.class.st

This file was deleted.

44 changes: 44 additions & 0 deletions source/Buoy-Comparison/HashCombinator.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"
I'm an abstract class defining the interface for hash combination. My intent is to provide a way of generate a hash by combining the hash of other objects.
"
Class {
#name : #HashCombinator,
#superclass : #Object,
#category : #'Buoy-Comparison'
}

{ #category : #combining }
HashCombinator >> combine: acumulatedHashValue with: hashValue [

self subclassResponsibility
]

{ #category : #combining }
HashCombinator >> combineAll: hashes [

^ self combineAll: hashes doing: [ :hash | hash ]
]

{ #category : #combining }
HashCombinator >> combineAll: aCollection doing: aMonadycBlock [

AssertionChecker
enforce: [ aCollection notEmpty ]
because: 'Cannot combine an empty collection of hashes'.

^ aCollection withoutFirst
inject: ( aMonadycBlock value: aCollection first )
into: [ :combined :each | self combine: combined with: ( aMonadycBlock value: each ) ]
]

{ #category : #combining }
HashCombinator >> combineHashOf: anObject with: anotherObject [

^ self combine: anObject hash with: anotherObject hash
]

{ #category : #combining }
HashCombinator >> combineHashesOfAll: aCollection [

^ self combineAll: aCollection doing: [ :object | object hash ]
]
7 changes: 7 additions & 0 deletions source/Buoy-Comparison/Object.extension.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Extension { #name : #Object }

{ #category : #'*Buoy-Comparison' }
Object >> equalityHashCombinator [

^ BitwiseExclusiveDisjunctionHashCombinator new
]
44 changes: 44 additions & 0 deletions source/Buoy-Deprecated/ExclusiveLogicalOr.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
Class {
#name : #ExclusiveLogicalOr,
#superclass : #Object,
#category : #'Buoy-Deprecated'
}

{ #category : #Computing }
ExclusiveLogicalOr class >> collecting: aBlock ofAll: anObjectCollection [

self
deprecated: 'Use self equalityHashCombinator'
transformWith:
' ExclusiveLogicalOr collecting: `@aBlock ofAll: `@anObjectCollection'
-> 'self equalityHashCombinator combineAll: `@collection doing: `@aBlock'.
^ self equalityHashCombinator combineAll: anObjectCollection doing: aBlock
]

{ #category : #testing }
ExclusiveLogicalOr class >> isDeprecated [

^ true
]

{ #category : #Computing }
ExclusiveLogicalOr class >> ofAll: anObjectCollection [

self
deprecated: 'Use self equalityHashCombinator'
transformWith:
' ExclusiveLogicalOr ofAll: `@collection'
-> 'self equalityHashCombinator combineAll: `@collection'.
^ self equalityHashCombinator combineAll: anObjectCollection
]

{ #category : #Computing }
ExclusiveLogicalOr class >> ofHashesOfAll: anObjectCollection [

self
deprecated: 'Use self equalityHashCombinator'
transformWith:
' ExclusiveLogicalOr ofHashesOfAll: `@collection'
-> 'self equalityHashCombinator combineHashesOfAll: `@collection'.
^ self equalityHashCombinator combineHashesOfAll: anObjectCollection
]
1 change: 1 addition & 0 deletions source/Buoy-Deprecated/package.st
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Package { #name : #'Buoy-Deprecated' }

0 comments on commit 02693d9

Please sign in to comment.