Skip to content

Commit

Permalink
Add commutative hash combiner.
Browse files Browse the repository at this point in the history
  • Loading branch information
StephenCleary committed Feb 13, 2021
1 parent 0b05034 commit b17fea6
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 0 deletions.
43 changes: 43 additions & 0 deletions src/Nito.Comparers.Core/Internals/CommutativeHashCombiner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Text;

#pragma warning disable CA1815, CA1721

namespace Nito.Comparers.Internals
{
/// <summary>
/// A hash combiner that is implemented with a simple commutative algorithm. This is a mutable struct for performance reasons.
/// </summary>
public struct CommutativeHashCombiner
{
private uint _hash;

/// <summary>
/// Gets the current result of the hash function.
/// </summary>
public int HashCode => unchecked((int) _hash);

/// <summary>
/// Creates a new hash, starting at <paramref name="seed"/>.
/// </summary>
/// <param name="seed">The seed for the hash. Defaults to the FNV hash offset, for no particular reason.</param>
public static CommutativeHashCombiner Create(int seed = unchecked((int)2166136261)) => new() { _hash = unchecked((uint) seed) };

/// <summary>
/// Adds the specified integer to this hash. This operation is commutative.
/// </summary>
/// <param name="data">The integer to hash.</param>
public void Combine(int data)
{
unchecked
{
// Simple addition is pretty much the best we can do since this operation must be commutative.
// We also add a constant value to act as a kind of "length counter" in the higher 16 bits.
// The hash combination is free to overflow into the "length counter".
// The higher 16 bits were chosen because that gives a decent distinction for sequences of <64k items while also distinguishing between small integer values (commonly used as ids).
_hash += (uint)data + 65536;
}
}
}
}
21 changes: 21 additions & 0 deletions test/UnitTests/Hashes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,26 @@ public void NullComparerHash_EqualsDefaultMurmer3Hash()
var objectHash = comparer.GetHashCode(0);
Assert.Equal(Murmur3Hash.Create().HashCode, objectHash);
}

[Fact]
public void CommutativeHashCombiner_IsCommutative()
{
// This unit test assumes the GetHashCode of these two objects are different.
int value1 = 5;
int value2 = 7;
Assert.NotEqual(value1.GetHashCode(), value2.GetHashCode());

var hash1 = CommutativeHashCombiner.Create();
hash1.Combine(value1);
hash1.Combine(value2);
var result1 = hash1.HashCode;

var hash2 = CommutativeHashCombiner.Create();
hash2.Combine(value2);
hash2.Combine(value1);
var result2 = hash2.HashCode;

Assert.Equal(result1, result2);
}
}
}

0 comments on commit b17fea6

Please sign in to comment.