Skip to content

Commit 97781b6

Browse files
committed
feat: Add support for custom hash functions (closes #1)
- Add IHashFunction interface for custom hash implementations - BKDRHash now implements IHashFunction - Options.HashFunction property allows custom hash functions - ColorHash uses custom hash if provided, otherwise defaults to BKDRHash - Add comprehensive tests for custom hash function functionality - Add documentation and examples in samples/CustomHashFunction.md This allows users to: - Use custom hash algorithms (MD5, SHA, etc.) - Implement domain-specific hash functions - Ensure cross-platform consistency - Create predictable hashes for testing Fixes #1
1 parent b6e1eea commit 97781b6

6 files changed

Lines changed: 237 additions & 4 deletions

File tree

samples/CustomHashFunction.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Custom Hash Function Example
2+
3+
This example demonstrates how to use a custom hash function with ColorHashSharp.
4+
5+
## Basic Usage with Custom Hash Function
6+
7+
```csharp
8+
using Fernandezja.ColorHashSharp;
9+
using Fernandezja.ColorHashSharp.Interfaces;
10+
11+
// 1. Create a custom hash function by implementing IHashFunction
12+
public class SimpleCharCodeHash : IHashFunction
13+
{
14+
public ulong Generate(string value)
15+
{
16+
if (string.IsNullOrEmpty(value))
17+
return 0;
18+
19+
ulong hash = 0;
20+
foreach (char c in value)
21+
{
22+
hash += (ulong)c;
23+
}
24+
return hash;
25+
}
26+
}
27+
28+
// 2. Use the custom hash function
29+
var customHash = new SimpleCharCodeHash();
30+
var options = new Options
31+
{
32+
HashFunction = customHash
33+
};
34+
35+
var colorHash = new ColorHash(options);
36+
37+
// Generate colors using your custom hash
38+
var hsl = colorHash.Hsl("Hello World");
39+
var rgb = colorHash.Rgb("Hello World");
40+
var hex = colorHash.Hex("Hello World");
41+
42+
Console.WriteLine($"HSL: H={hsl.H}, S={hsl.S}, L={hsl.L}");
43+
Console.WriteLine($"RGB: R={rgb.R}, G={rgb.G}, B={rgb.B}");
44+
Console.WriteLine($"Hex: {hex}");
45+
```
46+
47+
## Advanced Example: MD5-based Hash Function
48+
49+
```csharp
50+
using System.Security.Cryptography;
51+
using System.Text;
52+
53+
public class MD5HashFunction : IHashFunction
54+
{
55+
public ulong Generate(string value)
56+
{
57+
using (var md5 = MD5.Create())
58+
{
59+
byte[] hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(value));
60+
61+
// Convert first 8 bytes to ulong
62+
ulong hash = 0;
63+
for (int i = 0; i < 8 && i < hashBytes.Length; i++)
64+
{
65+
hash |= ((ulong)hashBytes[i]) << (i * 8);
66+
}
67+
return hash;
68+
}
69+
}
70+
}
71+
72+
// Use MD5-based hash
73+
var options = new Options
74+
{
75+
HashFunction = new MD5HashFunction(),
76+
Lightness = new List<double> { 0.5 },
77+
Saturation = new List<double> { 0.7 }
78+
};
79+
80+
var colorHash = new ColorHash(options);
81+
var color = colorHash.Hex("SecureString");
82+
```
83+
84+
## Why Use Custom Hash Functions?
85+
86+
- **Consistency across platforms**: Use a specific hash that behaves identically everywhere
87+
- **Security considerations**: Use cryptographic hashes for sensitive data
88+
- **Domain-specific needs**: Implement hash functions optimized for your data patterns
89+
- **Testing**: Create predictable hash functions for unit tests
90+
91+
## Default Behavior
92+
93+
If no custom hash function is specified, ColorHashSharp uses the default BKDRHash algorithm:
94+
95+
```csharp
96+
// These are equivalent:
97+
var colorHash1 = new ColorHash();
98+
var colorHash2 = new ColorHash(new Options()); // Uses default BKDRHash
99+
```
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
using Fernandezja.ColorHashSharp;
2+
using Fernandezja.ColorHashSharp.Interfaces;
3+
using System;
4+
using Xunit;
5+
6+
namespace ColorHashSharp.Tests
7+
{
8+
/// <summary>
9+
/// Custom simple hash function for testing
10+
/// </summary>
11+
public class SimpleCharCodeHash : IHashFunction
12+
{
13+
public ulong Generate(string value)
14+
{
15+
if (string.IsNullOrEmpty(value))
16+
return 0;
17+
18+
ulong hash = 0;
19+
foreach (char c in value)
20+
{
21+
hash += (ulong)c;
22+
}
23+
return hash;
24+
}
25+
}
26+
27+
public class CustomHashFunctionTest
28+
{
29+
[Fact(DisplayName = "CustomHashFunction_ShouldWork")]
30+
public void CustomHashFunction_ShouldWork()
31+
{
32+
// Arrange
33+
var customHash = new SimpleCharCodeHash();
34+
var options = new Options
35+
{
36+
HashFunction = customHash
37+
};
38+
var colorHash = new ColorHash(options);
39+
40+
// Act
41+
var hsl = colorHash.Hsl("Hello World");
42+
var rgb = colorHash.Rgb("Hello World");
43+
var hex = colorHash.Hex("Hello World");
44+
45+
// Assert
46+
Assert.NotNull(hsl);
47+
Assert.NotNull(rgb);
48+
Assert.NotNull(hex);
49+
Assert.True(hsl.H >= 0 && hsl.H < 360);
50+
Assert.True(hsl.S >= 0 && hsl.S <= 1);
51+
Assert.True(hsl.L >= 0 && hsl.L <= 1);
52+
}
53+
54+
[Fact(DisplayName = "CustomHashFunction_ShouldProduceDifferentResults")]
55+
public void CustomHashFunction_ShouldProduceDifferentResults()
56+
{
57+
// Arrange - Default BKDRHash
58+
var defaultColorHash = new ColorHash();
59+
var defaultHex = defaultColorHash.Hex("Test");
60+
61+
// Arrange - Custom simple hash
62+
var customHash = new SimpleCharCodeHash();
63+
var options = new Options
64+
{
65+
HashFunction = customHash
66+
};
67+
var customColorHash = new ColorHash(options);
68+
var customHex = customColorHash.Hex("Test");
69+
70+
// Assert - Different hash functions should produce different results
71+
Assert.NotEqual(defaultHex, customHex);
72+
}
73+
74+
[Fact(DisplayName = "CustomHashFunction_ShouldBeConsistent")]
75+
public void CustomHashFunction_ShouldBeConsistent()
76+
{
77+
// Arrange
78+
var customHash = new SimpleCharCodeHash();
79+
var options = new Options
80+
{
81+
HashFunction = customHash
82+
};
83+
var colorHash = new ColorHash(options);
84+
85+
// Act
86+
var hex1 = colorHash.Hex("Consistent");
87+
var hex2 = colorHash.Hex("Consistent");
88+
89+
// Assert - Same input should always produce same output
90+
Assert.Equal(hex1, hex2);
91+
}
92+
93+
[Fact(DisplayName = "Options_WithoutCustomHash_ShouldUseDefaultBKDRHash")]
94+
public void Options_WithoutCustomHash_ShouldUseDefaultBKDRHash()
95+
{
96+
// Arrange
97+
var options = new Options(); // No custom hash
98+
var colorHash = new ColorHash(options);
99+
100+
// Act
101+
var hex = colorHash.Hex("Hello World");
102+
103+
// Assert - Should produce expected result from BKDRHash
104+
Assert.NotNull(hex);
105+
Assert.Equal(6, hex.Length);
106+
}
107+
}
108+
}

src/ColorHashSharp/BKDRHash.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
using System.Diagnostics;
33
using System.Numerics;
44
using System.Text;
5+
using Fernandezja.ColorHashSharp.Interfaces;
56

67
namespace Fernandezja.ColorHashSharp
78
{
8-
public class BKDRHash
9+
public class BKDRHash : IHashFunction
910
{
1011
private const char PADDING_CHAR = 'x';
1112

src/ColorHashSharp/ColorHash.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace Fernandezja.ColorHashSharp
1111
public class ColorHash : IColorHash, IColorHashAlias
1212
{
1313
private readonly Options _options;
14-
private readonly BKDRHash _hashGenerator;
14+
private readonly IHashFunction _hashGenerator;
1515
private readonly ColorToHex _hexConverter;
1616

1717
public ColorHash()
@@ -23,8 +23,9 @@ public ColorHash()
2323

2424
public ColorHash(Options options)
2525
{
26-
_options = options;
27-
_hashGenerator = new BKDRHash();
26+
_options = options ?? throw new ArgumentNullException(nameof(options));
27+
// Use custom hash function if provided, otherwise use default BKDRHash
28+
_hashGenerator = _options.HashFunction ?? new BKDRHash();
2829
_hexConverter = new ColorToHex();
2930
}
3031

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
3+
namespace Fernandezja.ColorHashSharp.Interfaces
4+
{
5+
/// <summary>
6+
/// Interface for custom hash functions
7+
/// </summary>
8+
public interface IHashFunction
9+
{
10+
/// <summary>
11+
/// Generate hash from string value
12+
/// </summary>
13+
/// <param name="value">Input string</param>
14+
/// <returns>Hash as unsigned long</returns>
15+
ulong Generate(string value);
16+
}
17+
}

src/ColorHashSharp/Options.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Fernandezja.ColorHashSharp.Entities;
2+
using Fernandezja.ColorHashSharp.Interfaces;
23
using System;
34
using System.Collections.Generic;
45
using System.Text;
@@ -21,13 +22,19 @@ public class Options
2122
/// </summary>
2223
public List<double> L { get; set; }
2324

25+
/// <summary>
26+
/// Custom hash function. If null, BKDRHash will be used by default.
27+
/// </summary>
28+
public IHashFunction HashFunction { get; set; }
29+
2430
public Options()
2531
{
2632
//TODO: Get from options param
2733
S = GetLS(new List<double>() { 0.35, 0.5, 0.65 });
2834
L = GetLS(new List<double>() { 0.35, 0.5, 0.65 });
2935

3036
HueRanges = new List<Hue>();
37+
HashFunction = null; // Will use default BKDRHash
3138
}
3239

3340

0 commit comments

Comments
 (0)