-
Notifications
You must be signed in to change notification settings - Fork 5
/
SelectFuzzyMatch.ps1
134 lines (107 loc) · 4.18 KB
/
SelectFuzzyMatch.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
function Select-FuzzyMatch {
param($pattern, $str)
# Score consts
$adjacency_bonus = 5 # bonus for adjacent matches
$separator_bonus = 10 # bonus if match occurs after a separator
$camel_bonus = 10 # bonus if match is uppercase and prev is lower
$leading_letter_penalty = -3 # penalty applied for every letter in str before the first match
$max_leading_letter_penalty = -9 # maximum penalty for leading letters
$unmatched_letter_penalty = -1 # penalty for every letter that doesn't matter
# Loop variables
[int]$score = 0
$patternIdx = 0
$patternLength = $pattern.length
$strIdx = 0
$strLength = $str.length
$prevMatched = $false
$prevLower = $false
$prevSeparator = $true # true so if first letter match gets separator bonus
# Use "best" matched letter if multiple string letters match the pattern
$bestLetter = $null
$bestLower = $null
$bestLetterIdx = $null
$bestLetterScore = 0
$matchedIndices = @()
# Loop over strings
while ($strIdx -ne $strLength) {
[string]$patternChar = $null
if ($patternIdx -ne $patternLength) {
$patternChar = $pattern[$patternIdx]
}
[string]$strChar = $str[$strIdx]
if ($null -ne $patternChar) {
[string]$patternLower = $patternChar.ToLower()
}
$strLower = $strChar.ToLower()
$strUpper = $strChar.ToUpper()
$nextMatch = $patternChar -and $patternLower -eq $strLower
$rematch = $bestLetter -and $bestLower -eq $strLower
$advanced = $nextMatch -and $bestLetter
$patternRepeat = $bestLetter -and $patternChar -and $bestLower -eq $patternLower
if ($advanced -or $patternRepeat) {
$score += $bestLetterScore
$matchedIndices += $bestLetterIdx
$bestLetter = $null
$bestLower = $null
$bestLetterIdx = $null
$bestLetterScore = 0
}
if ($nextMatch -or $rematch) {
$newScore = 0;
# Apply penalty for each letter before the first pattern match
# Note: std::max because penalties are negative values. So max is smallest penalty.
if ($patternIdx -eq 0) {
$penalty = [Math]::max($strIdx * $leading_letter_penalty, $max_leading_letter_penalty)
$score += $penalty
}
# Apply bonus for consecutive bonuses
if ($prevMatched) {
$newScore += $adjacency_bonus
}
# Apply bonus for matches after a separator
if ($prevSeparator) {
$newScore += $separator_bonus
}
# Apply bonus across camel case boundaries. Includes "clever" isLetter check.
if ($prevLower -and $strChar -eq $strUpper -and $strLower -ne $strUpper) {
$newScore += $camel_bonus
}
# Update pattern index IF the next pattern letter was matched
if ($nextMatch) {
++$patternIdx
}
# Update best letter in str which may be for a "next" letter or a "rematch"
if ($newScore -ge $bestLetterScore) {
# Apply penalty for now skipped letter
if ($null -ne $bestLetter) {
$score += $unmatched_letter_penalty
}
$bestLetter = $strChar
$bestLower = $bestLetter.ToLower()
$bestLetterIdx = $strIdx
$bestLetterScore = $newScore
}
$prevMatched = $true
}
else {
# Append unmatch characters
$score += $unmatched_letter_penalty
$prevMatched = $false
}
# Includes "clever" isLetter check.
$prevLower = $strChar -eq $strLower -and $strLower -ne $strUpper
$prevSeparator = $strChar -eq '_' -or $strChar -eq ' '
++$strIdx
}
# Apply score for last match
if ($bestLetter) {
$score += $bestLetterScore
$matchedIndices += $bestLetterIdx
}
$matched = $patternIdx -eq $patternLength
[pscustomobject]@{
str = $str
matched = $matched
score = $score
}
}