using System.Extensions;
using System.Text.RegularExpressions;
namespace Daybreak.Shared.Utils;
public static partial class StringUtils
{
private const double SimilarityThreshold = 0.8;
private static readonly Regex SplitIntoWordsRegex = WordRegex();
public static int DamerauLevenshteinDistance(string s, string t)
{
var (height, width) = (s.Length + 1, t.Length + 1);
var matrix = new int[height, width];
for (var j = 0; j < height; j++) { matrix[j, 0] = j; };
for (var i = 0; i < width; i++) { matrix[0, i] = i; };
for (var j = 1; j < height; j++)
{
for (var i = 1; i < width; i++)
{
var cost = s[j - 1] == t[i - 1] ? 0 : 1;
var insertion = matrix[j, i - 1] + 1;
var deletion = matrix[j - 1, i] + 1;
var substitution = matrix[j - 1, i - 1] + cost;
var distance = Math.Min(insertion, Math.Min(deletion, substitution));
if (j > 1 && i > 1 && s[j - 1] == t[i - 2] && s[j - 2] == t[i - 1])
{
distance = Math.Min(distance, matrix[j - 2, i - 2] + cost);
}
matrix[j, i] = distance;
}
}
return matrix[height - 1, width - 1];
}
///
/// Returns true if stringToSearch is somewhat close to searchString.
///
///
///
/// True if strings match.
public static bool MatchesSearchString(string stringToSearch, string searchString, double threshold = SimilarityThreshold)
{
if (stringToSearch.IsNullOrWhiteSpace() ||
searchString.IsNullOrWhiteSpace())
{
return false;
}
if (stringToSearch.Length > searchString.Length
? stringToSearch.Contains(searchString, StringComparison.OrdinalIgnoreCase)
: searchString.Contains(stringToSearch, StringComparison.OrdinalIgnoreCase))
{
return true;
}
var distance = MatchSearchStringScore(stringToSearch, searchString);
var similarity = 1.0 - ((double)distance / searchString.Length);
return similarity >= threshold;
}
///
/// Returns the match string score
///
///
///
/// True if strings match.
public static int MatchSearchStringScore(string stringToSearch, string searchString)
{
return Math.Min(
DamerauLevenshteinDistance(stringToSearch.ToLower()[..Math.Min(stringToSearch.Length, searchString.Length)], searchString.ToLower()),
SplitIntoWordsRegex.Split(stringToSearch.ToLower())
.Select(word => DamerauLevenshteinDistance(word.ToLower()[..Math.Min(word.Length, searchString.Length)], searchString.ToLower())).Min());
}
[GeneratedRegex(@"\W+", RegexOptions.Compiled)]
private static partial Regex WordRegex();
}