Private/Invoke-FuzzyScore.ps1
|
<#
.SYNOPSIS Calculate fuzzy string similarity score. #> function Invoke-FuzzyScore { <# .SYNOPSIS Calculate Jaro-Winkler similarity between two strings. .DESCRIPTION Returns a score between 0.0 (no match) and 1.0 (exact match). Uses simplified Jaro-Winkler algorithm for fuzzy matching. .PARAMETER String1 First string to compare. .PARAMETER String2 Second string to compare. .OUTPUTS Double between 0.0 and 1.0. #> [CmdletBinding()] [OutputType([double])] param( [Parameter(Mandatory)] [string]$String1, [Parameter(Mandatory)] [string]$String2 ) if ($String1 -eq $String2) { return 1.0 } $len1 = $String1.Length $len2 = $String2.Length if ($len1 -eq 0 -or $len2 -eq 0) { return 0.0 } # Simplified Jaro distance $matchWindow = [Math]::Max($len1, $len2) / 2 - 1 $matchWindow = [Math]::Max(0, $matchWindow) $matches1 = New-Object bool[] $len1 $matches2 = New-Object bool[] $len2 $matches = 0 $transpositions = 0 # Find matches for ($i = 0; $i -lt $len1; $i++) { $start = [Math]::Max(0, $i - $matchWindow) $end = [Math]::Min($i + $matchWindow + 1, $len2) for ($j = $start; $j -lt $end; $j++) { if ($matches2[$j] -or $String1[$i] -ne $String2[$j]) { continue } $matches1[$i] = $true $matches2[$j] = $true $matches++ break } } if ($matches -eq 0) { return 0.0 } # Count transpositions $k = 0 for ($i = 0; $i -lt $len1; $i++) { if (-not $matches1[$i]) { continue } while (-not $matches2[$k]) { $k++ } if ($String1[$i] -ne $String2[$k]) { $transpositions++ } $k++ } $jaro = (($matches / $len1) + ($matches / $len2) + (($matches - $transpositions / 2) / $matches)) / 3.0 # Jaro-Winkler prefix bonus $prefix = 0 $maxPrefix = 4 for ($i = 0; $i -lt [Math]::Min([Math]::Min($len1, $len2), $maxPrefix); $i++) { if ($String1[$i] -eq $String2[$i]) { $prefix++ } else { break } } $jaroWinkler = $jaro + ($prefix * 0.1 * (1.0 - $jaro)) return $jaroWinkler } |