Class StringComparisonUtil
java.lang.Object
com.ssgllc.fish.service.util.registered.StringComparisonUtil
String similarity and distance metrics for dedupe match expressions and blocking sets.
Read the direction before scoring on one of these. The *Sim methods and
jaroWinklerDist(String, String) return a similarity, where 1.0 means identical.
jaccardDist(String, String), cosDist(String, String) and
levDist(String, String) return a distance, where 0.0 means identical.
jaroWinklerDist is named for Apache commons-text's JaroWinklerDistance, which
despite its name computes a similarity - a match expression that treats it as a distance scores
every pair backwards.
The q-gram methods (cosSim(String, String), jaccardSim(String, String))
compare bags of character q-grams, so they tolerate reordering; the edit-based ones do not.
-
Method Summary
Modifier and TypeMethodDescriptionstatic doubleCosine distance of two values - the complement ofcosSim(String, String).static doubleCosine similarity of two values, compared as bags of character q-grams. 1.0 is identical, 0.0 shares nothing.static StringdoubleMetaphone(String value) The primary Double Metaphone code of a value.static StringdoubleMetaphoneAlt(String value) The alternate Double Metaphone code of a value.doubleMetaphoneCodes(String value) The Double Metaphone codes of a value — the primary plus the alternate encoding, deduplicated (one entry when they coincide, two when the name has two plausible pronunciations).static booleandoubleMetaphoneMatch(String name, String nameToCompare) Whether two values match phonetically under Double Metaphone.static booleanisSoundexMatch(String name, String nameToCompare) Whether two values match phonetically under Soundex — i.e. their Soundex codes are equal (case-insensitively).static doublejaccardDist(String str, String strToCompare) Jaccard distance of two values - the complement ofjaccardSim(String, String).static doublejaccardSim(String str, String strToCompare) Jaccard similarity of two values: the proportion of their distinct character q-grams that they share. 1.0 is identical, 0.0 shares nothing.static doublejaroWinklerDist(String str, String strToCompare) Jaro-Winkler score of two values, which favours matches that agree at the start - useful for personal names, where typos cluster later in the string.static doubleLevenshtein edit distance of two values, normalised by the longer value's length - so this returns a proportion in[0.0, 1.0]rather than a raw edit count.static StringThe Soundex code of a value, for storing on an entity and matching in a blocking-set query (e.g. block ons.firstNameSoundex = {#soundex(firstName)}).
-
Method Details
-
cosSim
Cosine similarity of two values, compared as bags of character q-grams. 1.0 is identical, 0.0 shares nothing. Returns 0.0 if either value is null or empty. Tolerates reordered tokens, so it suits names and addresses better than an edit-based metric.- Parameters:
str- the first valuestrToCompare- the second value- Returns:
- a similarity in
[0.0, 1.0], higher meaning more alike
Groovy example:stringComparisonUtil.cosSim("John Smith", "Smith John")
SpEL example:#cosSim(#e.lastName, #p.lastName) * 40
Returns:a match-score contribution of up to 40 points
-
jaccardSim
Jaccard similarity of two values: the proportion of their distinct character q-grams that they share. 1.0 is identical, 0.0 shares nothing. Returns 0.0 if either value is null or empty. UnlikecosSim(String, String)it ignores how often each q-gram occurs, so repeated substrings count once.- Parameters:
str- the first valuestrToCompare- the second value- Returns:
- a similarity in
[0.0, 1.0], higher meaning more alike
Groovy example:stringComparisonUtil.jaccardSim("Main Street", "Main St")
SpEL example:#jaccardSim(#e.line1, #p.line1)
Returns:0.6
-
jaroWinklerDist
Jaro-Winkler score of two values, which favours matches that agree at the start - useful for personal names, where typos cluster later in the string. This is a similarity, not a distance: 1.0 means identical. The name follows Apache commons-text'sJaroWinklerDistance, which computes a similarity in spite of its name.- Parameters:
str- the first valuestrToCompare- the second value- Returns:
- a similarity in
[0.0, 1.0], higher meaning more alike
Groovy example:stringComparisonUtil.jaroWinklerDist("MARTHA", "MARHTA")
Returns:0.961
SpEL example:#jaroWinklerDist(#e.firstName, #p.firstName) > 0.9
Returns:true when the given names are near-identical
-
jaccardDist
Jaccard distance of two values - the complement ofjaccardSim(String, String). 0.0 means identical and larger values mean less alike.- Parameters:
str- the first valuestrToCompare- the second value- Returns:
- a distance in
[0.0, 1.0], lower meaning more alike
Groovy example:stringComparisonUtil.jaccardDist("abc", "abc")
Returns:0.0
SpEL example:#jaccardDist(#e.city, #p.city) < 0.2
Returns:true when the cities are close enough to treat as equal
-
cosDist
Cosine distance of two values - the complement ofcosSim(String, String). 0.0 means identical and larger values mean less alike.- Parameters:
str- the first valuestrToCompare- the second value- Returns:
- a distance in
[0.0, 1.0], lower meaning more alike
Groovy example:stringComparisonUtil.cosDist("hello", "hello")
Returns:0.0
SpEL example:#cosDist(#e.employerName, #p.employerName)
Returns:0.15
-
levDist
Levenshtein edit distance of two values, normalised by the longer value's length - so this returns a proportion in[0.0, 1.0]rather than a raw edit count. 0.0 means identical. One substitution in a three-character value scores 1/3, not 1. Normalising keeps a threshold meaningful across values of different lengths.- Parameters:
str- the first valuestrToCompare- the second value- Returns:
- edits divided by the length of the longer value, lower meaning more alike
Groovy example:stringComparisonUtil.levDist("cat", "car")
Returns:0.333
SpEL example:#levDist(#e.lastName, #p.lastName) < 0.25
Returns:true when at most a quarter of the surname differs
-
isSoundexMatch
Whether two values match phonetically under Soundex — i.e. their Soundex codes are equal (case-insensitively). Two nulls match; one null does not. Non-letter characters are stripped before encoding (viasoundex(String)), so accented/punctuated names don't error. The Double Metaphone equivalent — which also catches initial-sound variants like C/K that Soundex splits — isdoubleMetaphoneMatch(String, String).- Parameters:
name- the first valuenameToCompare- the second value- Returns:
- true if the two share the same Soundex code
Groovy example:stringComparisonUtil.isSoundexMatch("Smith", "Smyth")
SpEL example:#isSoundexMatch("Smith", "Smyth")
-
soundex
The Soundex code of a value, for storing on an entity and matching in a blocking-set query (e.g. block ons.firstNameSoundex = {#soundex(firstName)}). Accented characters are folded to their ASCII base (e.g.Muñoz → Munoz,François → Francois) and any remaining non-letters stripped before encoding, so accented and unaccented spellings encode alike. Returns null for a null, blank, or letter-free input.- Parameters:
value- the value to encode- Returns:
- the Soundex code, or null if there are no letters to encode
Groovy example:stringComparisonUtil.soundex("Robert")
SpEL example:#soundex("Robert")
-
doubleMetaphone
The primary Double Metaphone code of a value. Use this to populate a stored/computed phonetic column (firstNameDmeta = #doubleMetaphone(firstName)) that a blocking-set query then matches against. For the query side (matching on either pronunciation) usedoubleMetaphoneCodes(String). Returns null for a null/blank input or a value with no encodable letters.- Parameters:
value- the value to encode- Returns:
- the primary Double Metaphone code, or null if none
Groovy example:stringComparisonUtil.doubleMetaphone("Smith")
SpEL example:#doubleMetaphone("Smith")
-
doubleMetaphoneAlt
The alternate Double Metaphone code of a value. Double Metaphone emits a second code for names with two plausible pronunciations (often different-origin names); when there is no distinct alternate it equals the primary. Store this alongsidedoubleMetaphone(String)as a second phonetic column so a blocking-set query can match candidates on either pronunciation. Returns null for a null/blank input or a value with no encodable letters.- Parameters:
value- the value to encode- Returns:
- the alternate Double Metaphone code, or null if none
Groovy example:stringComparisonUtil.doubleMetaphoneAlt("Angelo")
SpEL example:#doubleMetaphoneAlt("Angelo")
-
doubleMetaphoneCodes
The Double Metaphone codes of a value — the primary plus the alternate encoding, deduplicated (one entry when they coincide, two when the name has two plausible pronunciations). Intended for the query side of a blocking-set query so a record matches candidates on either pronunciation, e.g.s.firstNameDmeta in ({#doubleMetaphoneCodes(firstName)}). Never returns null; returns an empty set for a null/blank input or a value with no encodable letters.- Parameters:
value- the value to encode- Returns:
- the distinct primary/alternate Double Metaphone codes; empty if none
Groovy example:stringComparisonUtil.doubleMetaphoneCodes("Zhang")
SpEL example:#doubleMetaphoneCodes("Zhang")
-
doubleMetaphoneMatch
Whether two values match phonetically under Double Metaphone. Matches when they share any code — primary or alternate — on either side (so it catches names that agree on only one of their two plausible pronunciations), which is looser and more recall-friendly than comparing primary codes alone. Two nulls match; one null does not (mirrorsisSoundexMatch(java.lang.String, java.lang.String)). The Soundex equivalent of this is the existingisSoundexMatch.- Parameters:
name- the first valuenameToCompare- the second value- Returns:
- true if the two are phonetically equal under Double Metaphone
Groovy example:stringComparisonUtil.doubleMetaphoneMatch("Smith", "Schmidt")
SpEL example:#doubleMetaphoneMatch("Smith", "Schmidt")
-