namespace allstarr.Services.Common; /// /// Provides fuzzy string matching for search result scoring. /// public static class FuzzyMatcher { /// /// Calculates a similarity score between two strings (0-100). /// Higher score means better match. /// public static int CalculateSimilarity(string query, string target) { if (string.IsNullOrWhiteSpace(query) || string.IsNullOrWhiteSpace(target)) { return 0; } var queryLower = query.ToLowerInvariant().Trim(); var targetLower = target.ToLowerInvariant().Trim(); // Exact match if (queryLower == targetLower) { return 100; } // Starts with query if (targetLower.StartsWith(queryLower)) { return 90; } // Contains query as whole word if (targetLower.Contains($" {queryLower} ") || targetLower.StartsWith($"{queryLower} ") || targetLower.EndsWith($" {queryLower}")) { return 80; } // Contains query anywhere if (targetLower.Contains(queryLower)) { return 70; } // Calculate Levenshtein distance for fuzzy matching var distance = LevenshteinDistance(queryLower, targetLower); var maxLength = Math.Max(queryLower.Length, targetLower.Length); if (maxLength == 0) { return 100; } // Convert distance to similarity score (0-60 range for fuzzy matches) var similarity = (1.0 - (double)distance / maxLength) * 60; return (int)Math.Max(0, similarity); } /// /// Calculates Levenshtein distance between two strings. /// private static int LevenshteinDistance(string source, string target) { if (string.IsNullOrEmpty(source)) { return target?.Length ?? 0; } if (string.IsNullOrEmpty(target)) { return source.Length; } var sourceLength = source.Length; var targetLength = target.Length; var distance = new int[sourceLength + 1, targetLength + 1]; for (var i = 0; i <= sourceLength; i++) { distance[i, 0] = i; } for (var j = 0; j <= targetLength; j++) { distance[0, j] = j; } for (var i = 1; i <= sourceLength; i++) { for (var j = 1; j <= targetLength; j++) { var cost = target[j - 1] == source[i - 1] ? 0 : 1; distance[i, j] = Math.Min( Math.Min(distance[i - 1, j] + 1, distance[i, j - 1] + 1), distance[i - 1, j - 1] + cost); } } return distance[sourceLength, targetLength]; } }