Skip to content

Commit

Permalink
Array and Guid tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
martinsmith1968 committed Jan 13, 2025
1 parent 1a2b878 commit bb5a4c3
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 12 deletions.
1 change: 1 addition & 0 deletions DNX.Extensions.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=oversized/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Populatable/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Southend/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=WORDIFY/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
20 changes: 20 additions & 0 deletions src/DNX.Extensions/Arrays/ArrayExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;

#pragma warning disable 1591

Expand Down Expand Up @@ -65,4 +67,22 @@ public static T[] ShiftRight<T>(this T[] input, int by = 1, T fillValue = defaul

return shiftedArray;
}

public static T[] Reduce<T>(this T[] inputArray, int targetSize, Func<T, T, T> method)
{
if (inputArray.Length <= targetSize)
return inputArray;

var result = new List<T>(inputArray.Take(targetSize));

for (var sourceIndex = targetSize; sourceIndex < inputArray.Length; ++sourceIndex)
{
var targetIndex = sourceIndex % targetSize;

var value = method(result[targetIndex], inputArray[sourceIndex]);
result[targetIndex] = value;
}

return result.ToArray();
}
}
50 changes: 40 additions & 10 deletions src/DNX.Extensions/Conversion/GuidExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System;
using System.Security.Cryptography;
using System.Text;
using DNX.Extensions.Arrays;

// ReSharper disable InconsistentNaming

namespace DNX.Extensions.Conversion;

Expand All @@ -9,26 +12,53 @@ namespace DNX.Extensions.Conversion;
/// </summary>
public static class GuidExtensions
{
/// <summary>
/// Convert any text item to a guid.
/// </summary>
public const int MAX_GUID_BYTE_ARRAY_SIZE = 16;

private static byte KeepOriginalValue(byte value1, byte value2) => value1;
private static byte ExclusiveOr(byte value1, byte value2) => (byte)(value1 ^ value2);

/// <summary>Convert any text item to a guid.</summary>
/// <param name="input">The text to convert</param>
/// <returns>A <see cref="Guid" /></returns>
/// <remarks>
/// The result is deterministic in that each text item will always generate the same result
/// NOTE: If the text item is actually a Guid, it will NOT be parsed directly to a Guid
/// </remarks>
/// <param name="input"></param>
/// <returns>
/// A <see cref="Guid"/>
/// </returns>
public static Guid ToDeterministicGuid(this string input)
{
input ??= string.Empty;
return ToDeterministicGuid(input, MD5.Create(), KeepOriginalValue);
}

//use MD5 hash to get a 16-byte hash of the string:
using var provider = new MD5CryptoServiceProvider();
/// <summary>Convert any text item to a guid.</summary>
/// <param name="input">The text to convert</param>
/// <param name="provider">The hash provider.</param>
/// <returns>A <see cref="Guid" /></returns>
/// <remarks>
/// The result is deterministic in that each text item will always generate the same result
/// NOTE: If the text item is actually a Guid, it will NOT be parsed directly to a Guid
/// </remarks>
public static Guid ToDeterministicGuid(this string input, HashAlgorithm provider)
{
return ToDeterministicGuid(input, provider, KeepOriginalValue);
}

/// <summary>Convert any text item to a guid.</summary>
/// <param name="input">The text to convert</param>
/// <param name="provider">The hash provider.</param>
/// <param name="reduceMethod">The method to use to reduce an oversized byte array to something a GUID will accept.</param>
/// <returns>A <see cref="Guid" /></returns>
/// <remarks>
/// The result is deterministic in that each text item will always generate the same result
/// NOTE: If the text item is actually a Guid, it will NOT be parsed directly to a Guid
/// </remarks>
public static Guid ToDeterministicGuid(this string input, HashAlgorithm provider, Func<byte, byte, byte> reduceMethod)
{
input ??= string.Empty;

var inputBytes = Encoding.Default.GetBytes(input);

var hashBytes = provider.ComputeHash(inputBytes);
hashBytes = hashBytes.Reduce(MAX_GUID_BYTE_ARRAY_SIZE, reduceMethod);

//generate a guid from the hash:
var hashGuid = new Guid(hashBytes);
Expand Down
91 changes: 90 additions & 1 deletion tests/DNX.Extensions.Tests/Arrays/ArrayExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using Xunit;
using Xunit.Abstractions;

// ReSharper disable UseUtf8StringLiteral

#pragma warning disable IDE0290

namespace DNX.Extensions.Tests.Arrays;
Expand Down Expand Up @@ -114,7 +116,6 @@ public void Test_ShiftLeft_null_array()
}
}


public class ShiftRight
{
[Fact]
Expand Down Expand Up @@ -170,4 +171,92 @@ public void Test_ShiftRight_null_array()
result.Length.Should().Be(0);
}
}

public class Reduce
{
[Theory]
[MemberData(nameof(Reduce_Data_byte))]
public void Reduce_can_produce_an_appropriate_output_for_bytes(byte[] input, int targetSize, Func<byte, byte, byte> method, byte[] expectedResult)
{
// Act
var result = input.Reduce(targetSize, method);

// Assert
result.Length.Should().BeLessThanOrEqualTo(targetSize);
result.Should().BeEquivalentTo(expectedResult);
}

[Theory]
[MemberData(nameof(Reduce_Data_string))]
public void Reduce_can_produce_an_appropriate_output_for_strings(string[] input, int targetSize, Func<string, string, string> method, string[] expectedResult)
{
// Act
var result = input.Reduce(targetSize, method);

// Assert
result.Length.Should().BeLessThanOrEqualTo(targetSize);
result.Should().BeEquivalentTo(expectedResult);
}

// Useful : https://www.compscilib.com/calculate/binaryxor?variation=default
public static TheoryData<byte[], int, Func<byte, byte, byte>, byte[]> Reduce_Data_byte()
{
var data = new TheoryData<byte[], int, Func<byte, byte, byte>, byte[]>();

data.Add(
[1, 2, 3, 4, 5, 6],
6,
Or,
[1, 2, 3, 4, 5, 6]
);
data.Add(
[1, 2, 3, 4, 5, 6],
8,
Or,
[1, 2, 3, 4, 5, 6]
);
data.Add(
[1, 2, 3, 4, 5, 6],
3,
Or,
[5, 7, 7]
);
data.Add(
[1, 2, 3, 4, 5, 6],
3,
XOr,
[5, 7, 5]
);
data.Add(
[10, 20, 30, 40, 50, 60],
3,
XOr,
[34, 38, 34]
);

return data;
}

public static TheoryData<string[], int, Func<string, string, string>, string[]> Reduce_Data_string()
{
var data = new TheoryData<string[], int, Func<string, string, string>, string[]>();

data.Add(
["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"],
5,
Concat,
["AF", "BG", "CH", "DI", "EJ"]
);

return data;
}

public static byte Or(byte value1, byte value2) => (byte)(value1 | value2);
public static byte XOr(byte value1, byte value2) => (byte)(value1 ^ value2);
public static byte And(byte value1, byte value2) => (byte)(value1 & value2);
public static byte Complement1(byte value1, byte value2) => (byte)(~value1);
public static byte Complement2(byte value1, byte value2) => (byte)(~value2);

public static string Concat(string value1, string value2) => value1 + value2;
}
}
40 changes: 39 additions & 1 deletion tests/DNX.Extensions.Tests/Conversion/GuidExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Security.Cryptography;
using DNX.Extensions.Conversion;
using FluentAssertions;
using Xunit;
Expand All @@ -24,6 +25,19 @@ public void ToDeterministicGuid_can_create_a_guid_from_any_string(string text)
result.ToString().Should().NotBe(text);
}

[Theory]
[MemberData(nameof(ToDeterministicGuid_custom_algorithm_Data))]
public void ToDeterministicGuid_can_create_a_guid_from_any_string_with_a_custom_algorithm(string text, HashAlgorithm algorithm)
{
// Act
var result = text.ToDeterministicGuid(algorithm);
outputHelper.WriteLine($"Text: {text} = {result}");

// Assert
result.Should().NotBe(Guid.Empty);
result.ToString().Should().NotBe(text);
}

[Theory]
[InlineData("ABC", "d2bd2f90-dfb1-4f0c-70b4-a5d23525e932")]
[InlineData("abc", "98500190-d23c-b04f-d696-3f7d28e17f72")]
Expand All @@ -44,4 +58,28 @@ public void ToDeterministicGuid_will_always_generate_a_predictable_result(string
result.Should().Be(result2);
result.ToString().Should().Be(expected);
}
}

// Useful : https://www.compscilib.com/calculate/binaryxor?variation=default
public static TheoryData<string, HashAlgorithm> ToDeterministicGuid_custom_algorithm_Data()
{
var algorithms = new HashAlgorithm[]
{
MD5.Create(),
SHA1.Create(),
SHA256.Create(),
};

var data = new TheoryData<string, HashAlgorithm>();

foreach (var algorithm in algorithms)
{
data.Add("ABC", algorithm);
data.Add("abc", algorithm);
data.Add("", algorithm);
data.Add(null, algorithm);
data.Add("497111F7-1511-49A8-8DB2-31B5E40953CB", algorithm);
}

return data;
}
}

0 comments on commit bb5a4c3

Please sign in to comment.