///|
fn algorithm_label(algorithm : Algorithm) -> String {
match algorithm {
Soundex => "Soundex"
RefinedSoundex => "RefinedSoundex"
Nysiis => "Nysiis"
Metaphone => "Metaphone"
DoubleMetaphone => "DoubleMetaphone"
Caverphone1 => "Caverphone1"
Caverphone2 => "Caverphone2"
MatchRating => "MatchRating"
}
}
///|
fn metric_label(metric : SimilarityMetric) -> String {
match metric {
Levenshtein => "Levenshtein"
Jaro => "Jaro"
JaroWinkler(_) => "JaroWinkler"
Dice(_) => "Dice"
}
}
///|
fn encoded_keys_intersect(left : EncodedKeys, right : EncodedKeys) -> Bool {
let left_values = left.all_non_empty_keys()
let right_values = right.all_non_empty_keys()
for left_value in left_values {
for right_value in right_values {
if left_value == right_value {
return true
}
}
}
false
}
///|
fn append_normalization_notices(
output : Array[String],
side : String,
notices : Array[NormalizationNotice],
) -> Unit {
for notice in notices {
match notice {
CharactersDropped(count) =>
output.push(side + ": characters dropped=" + "\{count}")
DiacriticsFolded(count) =>
output.push(side + ": diacritics folded=" + "\{count}")
InputTruncated(count) =>
output.push(side + ": input truncated=" + "\{count}")
}
}
}
///|
fn bounded_match_score(value : Double) -> Double {
if value < 0.0 {
0.0
} else if value > 1.0 {
1.0
} else {
value
}
}
///|
/// Compares two names and returns every score used by the final decision.
pub fn match_names(
left : String,
right : String,
config : MatchConfig,
) -> Result[MatchEvidence, MatchError] {
match validate_match_config(config) {
Err(error) => return Err(error)
Ok(_) => ()
}
let left_result = match normalize_name(left, config.normalization) {
Err(error) => return Err(NormalizationFailed(error))
Ok(result) => result
}
let right_result = match normalize_name(right, config.normalization) {
Err(error) => return Err(NormalizationFailed(error))
Ok(result) => result
}
let left_keys : Array[EncodedKeys] = []
let right_keys : Array[EncodedKeys] = []
let components : Array[ComponentScore] = []
let mut contribution_sum = 0.0
let mut weight_sum = 0.0
for encoder in config.encoders {
let left_encoded = encode_normalized_keys(
left_result.normalized,
encoder.algorithm,
)
let right_encoded = encode_normalized_keys(
right_result.normalized,
encoder.algorithm,
)
left_keys.push(left_encoded)
right_keys.push(right_encoded)
let raw_score = if encoded_keys_intersect(left_encoded, right_encoded) {
1.0
} else {
0.0
}
let contribution = raw_score * encoder.weight
components.push({
name: algorithm_label(encoder.algorithm),
raw_score,
weight: encoder.weight,
contribution,
})
contribution_sum = contribution_sum + contribution
weight_sum = weight_sum + encoder.weight
}
let string_result = match
matching_string_score(left_result, right_result, config) {
Err(error) => return Err(SimilarityFailed(error))
Ok(value) => value
}
let string_score = string_result.score
let string_contribution = string_score * config.string_weight
components.push({
name: metric_label(config.metric),
raw_score: string_score,
weight: config.string_weight,
contribution: string_contribution,
})
contribution_sum = contribution_sum + string_contribution
weight_sum = weight_sum + config.string_weight
let score = bounded_match_score(contribution_sum / weight_sum)
let notices : Array[String] = []
append_normalization_notices(notices, "left", left_result.notices)
append_normalization_notices(notices, "right", right_result.notices)
for notice in string_result.notices {
notices.push(notice)
}
Ok({
left_normalized: left_result.normalized,
right_normalized: right_result.normalized,
left_keys,
right_keys,
components,
score,
threshold: config.threshold,
matched: score >= config.threshold,
notices,
})
}