diff --git a/Autogram/AutogramBytesNoStringsV5g.cs b/Autogram/AutogramBytesNoStringsV5g.cs
new file mode 100644
index 0000000..dbb27dc
--- /dev/null
+++ b/Autogram/AutogramBytesNoStringsV5g.cs
@@ -0,0 +1,160 @@
+using System.Diagnostics;
+
+namespace Autogram
+{
+ ///
+ /// Based on version 5d, but uses the UnorderedByteSpanEqualsWithSum.
+ ///
+ public class AutogramBytesNoStringsV5g : IAutogramFinder
+ {
+ private readonly HashSet history = [];
+ private readonly Random random;
+
+ private readonly byte[] proposedCounts;
+ private readonly byte[] computedCounts;
+ private readonly byte[] proposedCountsBackup;
+
+ private readonly AutogramConfig config;
+
+ private readonly int variableAlphabetCount;
+
+ // Counts of chars that intersect with the chars that represent the numeric+plural
+ private readonly byte[] variableBaselineCount; // counts of characters in the template and conjunction PLUS the cardinals of the invariant characters.
+ private readonly byte[][][] variableNumericCounts;
+ // Minimum counts required for template, conjunction and the letter list that represents them.
+ // This will be used as the initial guess, and a lower limit for guesses.
+ private readonly byte[] variableMinimumCount;
+
+ public AutogramBytesNoStringsV5g(
+ AutogramConfig config,
+ int? randomSeed)
+ {
+ this.config = config ?? throw new ArgumentNullException(nameof(config));
+
+ random = randomSeed.HasValue ? new Random(randomSeed.Value) : new Random();
+
+ variableNumericCounts = config.GetVariableNumericCounts();
+
+ var variableChars = config.VariableChars.ToList();
+ variableAlphabetCount = variableChars.Count;
+ ArgumentOutOfRangeException.ThrowIfGreaterThan(variableAlphabetCount, ByteHistoryKey64.MaxLength);
+ variableBaselineCount = variableChars.Where(p => p.VariableBaselineCount.HasValue).Select(p => p.VariableBaselineCount!.Value).ToByteArray();
+ variableMinimumCount = variableChars.Select(p => p.MinimumCount).ToByteArray();
+
+ Debug.Assert(variableBaselineCount.Zip(variableMinimumCount).All(p => p.Second >= p.First));
+
+ proposedCounts = variableMinimumCount.ToArray();
+ computedCounts = variableMinimumCount.ToArray();
+ proposedCountsBackup = new byte[proposedCounts.Length];
+ UpdateComputedCounts();
+ }
+
+ ///
+ /// Iterates the autogram search process.
+ ///
+ /// The status of the current guess.
+ public Status Iterate()
+ {
+ var randomized = false;
+ var computedKey = new ByteHistoryKey64(computedCounts);
+
+ if (history.Contains(computedKey))
+ {
+ Randomize();
+ randomized = true;
+ }
+ else
+ {
+ computedCounts.CopyTo(proposedCounts);
+ history.Add(new ByteHistoryKey64(proposedCounts));
+ }
+
+ UpdateComputedCounts();
+
+ var reorderedEquals = computedCounts.AsSpan().UnorderedByteSpanEqualsWithSum(proposedCounts);
+
+ if (reorderedEquals)
+ {
+ reorderedEquals = computedCounts.AsSpan().SequenceEqual(proposedCounts) == false;
+ if (reorderedEquals)
+ {
+ computedCounts.CopyTo(proposedCounts);
+ }
+ return new Status(true, randomized, reorderedEquals);
+ }
+ else
+ {
+ return new Status(false, randomized, reorderedEquals);
+ }
+ }
+
+ private void UpdateComputedCounts()
+ {
+ variableBaselineCount.CopyTo(computedCounts.AsSpan());
+
+ for (var i = 0; i < variableAlphabetCount; i++)
+ {
+ var c = proposedCounts[i];
+ if (c == 0) continue;
+
+ var numericCount = variableNumericCounts[i][c];
+ for (var j = 0; j < variableAlphabetCount; j++)
+ {
+ computedCounts[j] += numericCount[j];
+ }
+ }
+
+#if DEBUG
+ for (var i = 0; i < variableAlphabetCount; i++)
+ {
+ Debug.Assert(computedCounts[i] >= variableMinimumCount[i]);
+ }
+#endif
+ }
+
+
+
+ private void Randomize()
+ {
+ proposedCounts.CopyTo(proposedCountsBackup);
+
+ var randomizationLevel = 1;
+ while (true)
+ {
+ for (int i = 0; i < proposedCounts.Length; i++)
+ {
+ var computedCount = computedCounts[i];
+ proposedCounts[i] = proposedCountsBackup[i] == computedCount
+ ? computedCount
+ : OffsetGuess(computedCount, variableMinimumCount[i], randomizationLevel);
+ }
+
+ ByteHistoryKey64 proposedKey = new(proposedCounts);
+ if (history.Contains(proposedKey) == false)
+ {
+ history.Add(proposedKey);
+ return;
+ }
+
+ randomizationLevel++;
+ }
+ }
+
+ private byte OffsetGuess(byte actualCount, byte minimumCount, int modifier)
+ {
+ int min = Math.Max(minimumCount, actualCount - modifier);
+ int max = Math.Min(byte.MaxValue, actualCount + modifier + 1); // +1 because Random.Next is exclusive at upper bound
+ return (byte)random.Next(min, max);
+ }
+
+ public AutogramSnapshot GetAutogramSnapshot()
+ {
+ return new AutogramSnapshot(config.AllChars.Select(p => (
+ p.Char,
+ Count: p.VariableIndex.HasValue ? (int)proposedCounts[p.VariableIndex.Value] : p.MinimumCount
+ )).Where(p => p.Count > 0));
+ }
+
+ public int HistoryCount => history.Count;
+ }
+}
diff --git a/Autogram/ByteHistoryKey64.cs b/Autogram/ByteHistoryKey64.cs
index 2b464bc..fde579b 100644
--- a/Autogram/ByteHistoryKey64.cs
+++ b/Autogram/ByteHistoryKey64.cs
@@ -51,10 +51,7 @@ public override bool Equals(object? obj)
public override int GetHashCode()
{
- return HashCode.Combine(
- HashCode.Combine(length, chunk0, chunk1, chunk2),
- HashCode.Combine(chunk3, chunk4, chunk5, chunk6),
- chunk7);
+ return HashCode.Combine(chunk0, chunk1, chunk2, chunk3, chunk4, chunk5, chunk6, chunk7);
}
private static ulong ReadChunk(ReadOnlySpan values, int offset)
diff --git a/Autogram/Extensions.cs b/Autogram/Extensions.cs
index 2cfd00d..0c13a5e 100644
--- a/Autogram/Extensions.cs
+++ b/Autogram/Extensions.cs
@@ -195,6 +195,45 @@ public static bool UnorderedByteSpanEquals(this ReadOnlySpan a, ReadOnlySp
return true;
}
+ public static bool UnorderedByteSpanEqualsWithSum(this ReadOnlySpan a, ReadOnlySpan b)
+ {
+ if (a.Length != b.Length)
+ {
+ return false;
+ }
+
+ int sumA = 0;
+ int sumB = 0;
+ for (int i = 0; i < a.Length; i++)
+ {
+ sumA += a[i];
+ sumB += b[i];
+ }
+
+ if (sumA != sumB)
+ {
+ return false;
+ }
+
+ Span counts = stackalloc int[256];
+
+ foreach (var b1 in a)
+ {
+ counts[b1]++;
+ }
+
+ foreach (var b2 in b)
+ {
+ if (--counts[b2] < 0)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
public static bool UnorderedByteSpanEquals2(this ReadOnlySpan x, ReadOnlySpan y)
{
if (x.Length != y.Length)
diff --git a/AutogramBenchmark/AutogramBenchmark.cs b/AutogramBenchmark/AutogramBenchmark.cs
index e4aba32..4943448 100644
--- a/AutogramBenchmark/AutogramBenchmark.cs
+++ b/AutogramBenchmark/AutogramBenchmark.cs
@@ -16,6 +16,7 @@ public class AutogramBenchmark
private List solver5cList = null!;
private List solver5dList = null!;
private List solver5eList = null!;
+ private List solver5gList = null!;
private List solver6List = null!;
private List solver7List = null!;
private List solver8List = null!;
@@ -91,6 +92,12 @@ public void IterationSetupV5e()
solver5eList = CreateSolvers(SeedCount, p => new AutogramBytesNoStringsV5e(autogramConfig, p));
}
+ [IterationSetup(Targets = new[] { nameof(AutogramBytesNoStringsV5g_Solve_Average_Batched_Seeds) })]
+ public void IterationSetupV5g()
+ {
+ solver5gList = CreateSolvers(SeedCount, p => new AutogramBytesNoStringsV5g(autogramConfig, p));
+ }
+
//[IterationSetup(Targets = new[] { nameof(AutogramBytesNoStringsV5a_Solve_Average_Batched_Seeds) })]
//public void IterationSetupV5a()
//{
@@ -151,6 +158,12 @@ public void CleanupIterationV5e()
ClearSolvers(ref solver5eList);
}
+ [IterationCleanup(Targets = new[] { nameof(AutogramBytesNoStringsV5g_Solve_Average_Batched_Seeds) })]
+ public void CleanupIterationV5g()
+ {
+ ClearSolvers(ref solver5gList);
+ }
+
//[IterationCleanup(Targets = new[] { nameof(AutogramBytesNoStringsV5a_Solve_Average_Batched_Seeds) })]
//public void CleanupIterationV5a()
//{
@@ -210,6 +223,13 @@ public void AutogramBytesNoStringsV5e_Solve_Average_Batched_Seeds()
{
SolveAll(solver5eList);
}
+
+ [Benchmark]
+ public void AutogramBytesNoStringsV5g_Solve_Average_Batched_Seeds()
+ {
+ SolveAll(solver5gList);
+ }
+
//[Benchmark]
//public void AutogramBytesNoStringsV5a_Solve_Average_Batched_Seeds()
//{
diff --git a/AutogramBenchmark/UnorderedByteArrayComparisonBenchmark.cs b/AutogramBenchmark/UnorderedByteArrayComparisonBenchmark.cs
index 5c99e78..15a01aa 100644
--- a/AutogramBenchmark/UnorderedByteArrayComparisonBenchmark.cs
+++ b/AutogramBenchmark/UnorderedByteArrayComparisonBenchmark.cs
@@ -7,10 +7,12 @@ public class UnorderedByteArrayComparisonBenchmark
{
private const int TestDataSize = 1000;
private const int V = 26;
- private byte[][] testdata = new byte[TestDataSize][];
+ private byte[][] testdata = null!;
- public UnorderedByteArrayComparisonBenchmark()
+ [GlobalSetup]
+ public void GlobalSetup()
{
+ testdata = new byte[TestDataSize][];
var random = new Random(1);
for (int i = 0; i < TestDataSize; i++)
@@ -24,40 +26,80 @@ public UnorderedByteArrayComparisonBenchmark()
}
}
- [Benchmark]
- public void UnorderedByteArrayComparer()
+ [Benchmark(OperationsPerInvoke = TestDataSize * TestDataSize)]
+ public int UnorderedByteArrayComparer()
{
+ var matches = 0;
+
foreach(var arr in testdata)
{
foreach (var arr2 in testdata)
{
- var matches = ((ReadOnlySpan)arr.AsSpan()).UnorderedByteSpanEquals(arr2);
+ if (((ReadOnlySpan)arr.AsSpan()).UnorderedByteSpanEquals(arr2))
+ {
+ matches++;
+ }
}
}
+
+ return matches;
}
- [Benchmark]
- public void UnorderedByteArrayComparer2()
+ [Benchmark(OperationsPerInvoke = TestDataSize * TestDataSize)]
+ public int UnorderedByteArrayComparerWithSum()
{
+ var matches = 0;
+
foreach (var arr in testdata)
{
foreach (var arr2 in testdata)
{
- var matches = ((ReadOnlySpan)arr.AsSpan()).UnorderedByteSpanEquals2(arr2);
+ if (((ReadOnlySpan)arr.AsSpan()).UnorderedByteSpanEqualsWithSum(arr2))
+ {
+ matches++;
+ }
}
}
+
+ return matches;
}
- [Benchmark]
- public void ByteArraysHaveSameContents()
+ [Benchmark(OperationsPerInvoke = TestDataSize * TestDataSize)]
+ public int UnorderedByteArrayComparer2()
{
+ var matches = 0;
+
foreach (var arr in testdata)
{
foreach (var arr2 in testdata)
{
- var matches = ((ReadOnlySpan)arr.AsSpan()).ByteArraysHaveSameContents(arr2);
+ if (((ReadOnlySpan)arr.AsSpan()).UnorderedByteSpanEquals2(arr2))
+ {
+ matches++;
+ }
}
}
+
+ return matches;
+ }
+
+ [Benchmark(OperationsPerInvoke = TestDataSize * TestDataSize)]
+ public int ByteArraysHaveSameContents()
+ {
+ var matches = 0;
+
+ foreach (var arr in testdata)
+ {
+ foreach (var arr2 in testdata)
+ {
+ if (((ReadOnlySpan)arr.AsSpan()).ByteArraysHaveSameContents(arr2))
+ {
+ matches++;
+ }
+ }
+ }
+
+ return matches;
}
}
diff --git a/AutogramTest/AutogramTest.cs b/AutogramTest/AutogramTest.cs
index f146fe9..bdd3a79 100644
--- a/AutogramTest/AutogramTest.cs
+++ b/AutogramTest/AutogramTest.cs
@@ -102,6 +102,14 @@ public void TestV5e()
1769732);
}
+ [Fact]
+ public void TestV5g()
+ {
+ RunAutogramTest(
+ (config, seed) => new AutogramBytesNoStringsV5g(config, seed),
+ ExpectedIterations);
+ }
+
[Fact]
public void TestV6()
{