From 800d3ec13e257083a8933db357f35f5b03528521 Mon Sep 17 00:00:00 2001 From: Leszek Bochenek Date: Tue, 8 Jul 2025 23:33:02 +0200 Subject: [PATCH] Add `ReadonlySpan` API closes #15 --- src/Fastenshtein/AutoCompleteLevenshtein.cs | 23 ++++++++++++++++----- src/Fastenshtein/Fastenshtein.csproj | 13 ++++++++++++ src/Fastenshtein/Levenshtein.cs | 20 ++++++++++++++---- src/Fastenshtein/StaticLevenshtein.cs | 23 ++++++++++++++++----- 4 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/Fastenshtein/AutoCompleteLevenshtein.cs b/src/Fastenshtein/AutoCompleteLevenshtein.cs index b9046d9..00a1820 100644 --- a/src/Fastenshtein/AutoCompleteLevenshtein.cs +++ b/src/Fastenshtein/AutoCompleteLevenshtein.cs @@ -1,4 +1,6 @@ -namespace Fastenshtein +using System; + +namespace Fastenshtein { /// /// Measures the difference between two strings. @@ -14,15 +16,26 @@ public static class AutoCompleteLevenshtein /// 0 exact match less a positive value, lower the value the best match public static int Distance(string value1, string value2) { - if (value1.Length == 0) + return Distance(value1.AsSpan(), value2.AsSpan()); + } + + /// + /// Compares the two spans of characters and returns a measure of their summarily with 0 being an exact match. + /// + /// the incomplete value entered by the user + /// the value to compare value1 against + /// 0 exact match less a positive value, lower the value the best match + public static unsafe int Distance(ReadOnlySpan value1, ReadOnlySpan value2) + { + if (value1.IsEmpty) { return 0; } - int[] costs = new int[value1.Length]; + int* costs = stackalloc int[value1.Length]; // Add indexing for insertion to first row - for (int i = 0; i < costs.Length;) + for (int i = 0; i < value1.Length;) { costs[i] = ++i; } @@ -69,7 +82,7 @@ public static int Distance(string value1, string value2) } } - return costs[costs.Length - 1]; + return costs[value1.Length - 1]; } } } diff --git a/src/Fastenshtein/Fastenshtein.csproj b/src/Fastenshtein/Fastenshtein.csproj index 47d7df5..743f840 100644 --- a/src/Fastenshtein/Fastenshtein.csproj +++ b/src/Fastenshtein/Fastenshtein.csproj @@ -34,6 +34,18 @@ + + + + + + + + + + + + all @@ -56,6 +68,7 @@ true enable + true diff --git a/src/Fastenshtein/Levenshtein.cs b/src/Fastenshtein/Levenshtein.cs index 085b2b1..c889fa6 100644 --- a/src/Fastenshtein/Levenshtein.cs +++ b/src/Fastenshtein/Levenshtein.cs @@ -1,4 +1,6 @@ -namespace Fastenshtein +using System; + +namespace Fastenshtein { /// /// Measures the difference between two strings. @@ -7,7 +9,7 @@ public partial class Levenshtein { /* - * WARRING this class is performance critical (Speed). + * WARNING this class is performance critical (Speed). */ private readonly string _storedValue; @@ -30,11 +32,21 @@ public Levenshtein(string value) public int StoredLength => _storedValue.Length; /// - /// Compares a value to the stored value. + /// Compares a value to the stored value. /// Not thread safe. /// /// Difference. 0 complete match. public int DistanceFrom(string value) + { + return DistanceFrom(value.AsSpan()); + } + + /// + /// Compares a value to the stored value. + /// Not thread safe. + /// + /// Difference. 0 complete match. + public int DistanceFrom(ReadOnlySpan value) { // copying to local variables allows JIT to remove bounds checks, as it understands they can not change var costs = _costs; @@ -42,7 +54,7 @@ public int DistanceFrom(string value) if (costs.Length == 0 // this will never be ture, however it allows the JIT to remove a bounds check - || costs.Length != storedValue.Length) + || costs.Length != storedValue.Length) { return value.Length; } diff --git a/src/Fastenshtein/StaticLevenshtein.cs b/src/Fastenshtein/StaticLevenshtein.cs index deaf457..904042b 100644 --- a/src/Fastenshtein/StaticLevenshtein.cs +++ b/src/Fastenshtein/StaticLevenshtein.cs @@ -1,4 +1,6 @@ -namespace Fastenshtein +using System; + +namespace Fastenshtein { /// /// Measures the difference between two strings. @@ -7,21 +9,32 @@ public partial class Levenshtein { /// - /// Compares the two values to find the minimum Levenshtein distance. + /// Compares the two values to find the minimum Levenshtein distance. /// Thread safe. /// /// Difference. 0 complete match. public static int Distance(string value1, string value2) + { + return Distance(value1.AsSpan(), value2.AsSpan()); + } + + /// + /// Compares the two values to find the minimum Levenshtein distance. + /// Thread safe. + /// + /// Difference. 0 complete match. + public static unsafe int Distance(ReadOnlySpan value1, ReadOnlySpan value2) { if (value2.Length == 0) { return value1.Length; } - int[] costs = new int[value2.Length]; + int costsLength = value2.Length; + int* costs = stackalloc int[costsLength]; // Add indexing for insertion to first row - for (int i = 0; i < costs.Length;) + for (int i = 0; i < costsLength;) { costs[i] = ++i; } @@ -66,7 +79,7 @@ public static int Distance(string value1, string value2) } } - return costs[costs.Length - 1]; + return costs[costsLength - 1]; } } }