github.com/rogpeppe/go-internal@v1.12.1-0.20240509064211-c8567cf8e95f/internal/misspell/misspell.go (about) 1 // Package misspell impements utilities for basic spelling correction. 2 package misspell 3 4 import ( 5 "unicode/utf8" 6 ) 7 8 // AlmostEqual reports whether a and b have Damerau-Levenshtein distance of at 9 // most 1. That is, it reports whether a can be transformed into b by adding, 10 // removing or substituting a single rune, or by swapping two adjacent runes. 11 // Invalid runes are considered equal. 12 // 13 // It runs in O(len(a)+len(b)) time. 14 func AlmostEqual(a, b string) bool { 15 for len(a) > 0 && len(b) > 0 { 16 ra, tailA := shiftRune(a) 17 rb, tailB := shiftRune(b) 18 if ra == rb { 19 a, b = tailA, tailB 20 continue 21 } 22 // check for addition/deletion/substitution 23 if equalValid(a, tailB) || equalValid(tailA, b) || equalValid(tailA, tailB) { 24 return true 25 } 26 if len(tailA) == 0 || len(tailB) == 0 { 27 return false 28 } 29 // check for swap 30 a, b = tailA, tailB 31 Ra, tailA := shiftRune(tailA) 32 Rb, tailB := shiftRune(tailB) 33 return ra == Rb && Ra == rb && equalValid(tailA, tailB) 34 } 35 if len(a) == 0 { 36 return len(b) == 0 || singleRune(b) 37 } 38 return singleRune(a) 39 } 40 41 // singleRune reports whether s consists of a single UTF-8 codepoint. 42 func singleRune(s string) bool { 43 _, n := utf8.DecodeRuneInString(s) 44 return n == len(s) 45 } 46 47 // shiftRune splits off the first UTF-8 codepoint from s and returns it and the 48 // rest of the string. It panics if s is empty. 49 func shiftRune(s string) (rune, string) { 50 if len(s) == 0 { 51 panic(s) 52 } 53 r, n := utf8.DecodeRuneInString(s) 54 return r, s[n:] 55 } 56 57 // equalValid reports whether a and b are equal, if invalid code points are considered equal. 58 func equalValid(a, b string) bool { 59 var ra, rb rune 60 for len(a) > 0 && len(b) > 0 { 61 ra, a = shiftRune(a) 62 rb, b = shiftRune(b) 63 if ra != rb { 64 return false 65 } 66 } 67 return len(a) == 0 && len(b) == 0 68 }