Skip to content

Commit

Permalink
SE: Enumerable ContentHash for ordered and unordered sets (#6979)
Browse files Browse the repository at this point in the history
  • Loading branch information
martin-strecker-sonarsource authored and pavel-mikula-sonarsource committed Mar 28, 2023
1 parent 41cc7cf commit 447a1d7
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 12 deletions.
14 changes: 12 additions & 2 deletions analyzers/src/SonarAnalyzer.Common/Helpers/HashCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,22 @@ namespace SonarAnalyzer.Helpers
private const uint PreMultiplier = 3266489917U;
private const uint PostMultiplier = 668265263U;
private const int RotateOffset = 17;
private const int IntSeed = 393241;

public static int DictionaryContentHash<TKey, TValue>(IDictionary<TKey, TValue> dictionary) =>
dictionary.Aggregate(0, (seed, kvp) => seed ^ Combine(kvp.Key, kvp.Value));

public static int EnumerableContentHash<TValue>(IEnumerable<TValue> enumerable) =>
enumerable.Aggregate(0, (seed, x) => Combine(seed, x));
/// <summary>
/// Calculates a hash for the enumerable based on the content. The same values in a different order produce the same hash-code.
/// </summary>
public static int EnumerableUnorderedContentHash<TValue>(IEnumerable<TValue> enumerable) =>
enumerable.Aggregate(IntSeed, (seed, x) => seed ^ (x?.GetHashCode() ?? 0));

/// <summary>
/// Calculates a hash for the enumerable based on the content. The same values in a different order produce different hash-codes.
/// </summary>
public static int EnumerableOrderedContentHash<TValue>(IEnumerable<TValue> enumerable) =>
enumerable.Aggregate(IntSeed, Combine);

public static int Combine<T1, T2>(T1 a, T2 b) =>
(int)Seed.AddHash(a?.GetHashCode()).AddHash(b?.GetHashCode());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ public override int GetHashCode() =>
HashCode.DictionaryContentHash(OperationValue),
HashCode.DictionaryContentHash(SymbolValue),
HashCode.DictionaryContentHash(CaptureOperation),
HashCode.EnumerableContentHash(PreservedSymbols),
HashCode.EnumerableContentHash(Exceptions));
HashCode.EnumerableUnorderedContentHash(PreservedSymbols),
HashCode.EnumerableOrderedContentHash(Exceptions));

public bool Equals(ProgramState other) =>
// VisitCount is not compared, two ProgramState are equal if their current state is equal. No matter what historical path led to it.
Expand Down
84 changes: 76 additions & 8 deletions analyzers/tests/SonarAnalyzer.UnitTest/Helpers/HashCodeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

#pragma warning disable CA1825 // Avoid zero-length array allocations

using HashCode = SonarAnalyzer.Helpers.HashCode;

namespace SonarAnalyzer.UnitTest.Helpers
{
[TestClass]
Expand All @@ -28,10 +32,10 @@ public class HashCodeTest
[DataRow("Lorem Ipsum")]
public void Combine_ProducesDifferentResults(string input)
{
var hash2 = SonarAnalyzer.Helpers.HashCode.Combine(input, input);
var hash3 = SonarAnalyzer.Helpers.HashCode.Combine(input, input, input);
var hash4 = SonarAnalyzer.Helpers.HashCode.Combine(input, input, input, input);
var hash5 = SonarAnalyzer.Helpers.HashCode.Combine(input, input, input, input, input);
var hash2 = HashCode.Combine(input, input);
var hash3 = HashCode.Combine(input, input, input);
var hash4 = HashCode.Combine(input, input, input, input);
var hash5 = HashCode.Combine(input, input, input, input, input);

hash2.Should().NotBe(0);
hash3.Should().NotBe(0).And.NotBe(hash2);
Expand All @@ -45,8 +49,8 @@ public void DictionaryContentHash_StableForUnsortedDictionary()
var numbers = Enumerable.Range(1, 1000);
var dict1 = numbers.ToDictionary(x => x, x => x);
var dict2 = numbers.OrderByDescending(x => x).ToDictionary(x => x, x => x);
var hashCode1 = SonarAnalyzer.Helpers.HashCode.DictionaryContentHash(dict1);
var hashCode2 = SonarAnalyzer.Helpers.HashCode.DictionaryContentHash(dict2);
var hashCode1 = HashCode.DictionaryContentHash(dict1);
var hashCode2 = HashCode.DictionaryContentHash(dict2);
hashCode1.Should().Be(hashCode2);
}

Expand All @@ -56,9 +60,73 @@ public void DictionaryContentHash_StableForImmutableDictionary()
var numbers = Enumerable.Range(1, 1000);
var dict1 = numbers.ToImmutableDictionary(x => x, x => x);
var dict2 = numbers.OrderByDescending(x => x).ToImmutableDictionary(x => x, x => x);
var hashCode1 = SonarAnalyzer.Helpers.HashCode.DictionaryContentHash(dict1);
var hashCode2 = SonarAnalyzer.Helpers.HashCode.DictionaryContentHash(dict2);
var hashCode1 = HashCode.DictionaryContentHash(dict1);
var hashCode2 = HashCode.DictionaryContentHash(dict2);
hashCode1.Should().Be(hashCode2);
}

[TestMethod]
public void EnumerableUnorderedContentHash_Empty()
{
var ints = new int[0];
var strings = new string[0];

HashCode.EnumerableUnorderedContentHash(ints).Should().Be(HashCode.EnumerableUnorderedContentHash(new int[0]));
HashCode.EnumerableUnorderedContentHash(strings).Should().Be(HashCode.EnumerableUnorderedContentHash(strings));
HashCode.EnumerableUnorderedContentHash(ints).Should().Be(HashCode.EnumerableUnorderedContentHash(strings));
}

[TestMethod]
public void EnumerableUnorderedContentHash_Order()
{
var ints1 = new[] { 0, 1, 2 };
var ints2 = new[] { 2, 1, 0 };
var ints3 = new[] { 0, 1, 8 };

HashCode.EnumerableUnorderedContentHash(ints1).Should().Be(HashCode.EnumerableUnorderedContentHash(ints2)).And.NotBe(0);
HashCode.EnumerableUnorderedContentHash(ints1).Should().NotBe(HashCode.EnumerableUnorderedContentHash(ints3));
}

[TestMethod]
public void EnumerableUnorderedContentHash_DifferentLength()
{
var ints1 = new[] { 0, 1, 2 };
var ints2 = new[] { 0, 1, 2, 3 };

HashCode.EnumerableUnorderedContentHash(ints1).Should().NotBe(HashCode.EnumerableUnorderedContentHash(ints2));
}

[TestMethod]
public void EnumerableOrderedContentHash_Empty()
{
var ints = new int[0];
var strings = new string[0];

HashCode.EnumerableOrderedContentHash(ints).Should().Be(HashCode.EnumerableOrderedContentHash(new int[0]));
HashCode.EnumerableOrderedContentHash(strings).Should().Be(HashCode.EnumerableOrderedContentHash(strings));
HashCode.EnumerableOrderedContentHash(ints).Should().Be(HashCode.EnumerableOrderedContentHash(strings));
}

[TestMethod]
public void EnumerableOrderedContentHash_Order()
{
var ints1 = new[] { 0, 1, 2 };
var ints2 = new[] { 0, 1, 2 };
var ints3 = new[] { 2, 1, 0 };
var ints4 = new[] { 0, 1, 8 };

HashCode.EnumerableOrderedContentHash(ints1).Should().Be(HashCode.EnumerableOrderedContentHash(ints2)).And.NotBe(0);
HashCode.EnumerableOrderedContentHash(ints1).Should().NotBe(HashCode.EnumerableOrderedContentHash(ints3));
HashCode.EnumerableOrderedContentHash(ints1).Should().NotBe(HashCode.EnumerableOrderedContentHash(ints4));
}

[TestMethod]
public void EnumerableOrderedContentHash_DifferentLength()
{
var ints1 = new[] { 0, 1, 2 };
var ints2 = new[] { 0, 1, 2, 3 };

HashCode.EnumerableOrderedContentHash(ints1).Should().NotBe(HashCode.EnumerableOrderedContentHash(ints2));
}
}
}

0 comments on commit 447a1d7

Please sign in to comment.