///|
/// String similarity comparison functionality
///
/// Provides similarity comparison functionality based on string distance algorithms
/// Uses Levenshtein distance algorithm by default
///|
/// Similarity comparison options
pub struct CompareSimilarityOptions {
/// Whether to be case sensitive
/// Default value: false
case_sensitive : Bool
/// Custom comparison function
/// Parameters: (a, b) -> distance between two strings
/// Default value: levenshtein_distance
compare_fn : (String, String) -> Int
}
///|
/// Create default similarity comparison options
pub fn default_compare_similarity_options() -> CompareSimilarityOptions {
CompareSimilarityOptions::{
case_sensitive: false,
compare_fn: levenshtein_distance,
}
}
///|
/// Create case-sensitive similarity comparison options
pub fn case_sensitive_compare_similarity_options() -> CompareSimilarityOptions {
CompareSimilarityOptions::{
case_sensitive: true,
compare_fn: levenshtein_distance,
}
}
///|
/// Get string similarity comparison function
///
/// Generate a comparator function to determine which of two strings is more similar to a given string
/// Uses Levenshtein distance algorithm by default to calculate distance between words
///
/// * `given_word` - The string for measuring distance
/// * `options` - Comparison options
/// + Returns a comparator function that returns negative if a is more similar than b, positive if b is more similar, 0 if same
///
/// # Examples
/// Most similar words will be sorted at the front of the array
/// ```
/// let words = ["hi", "hello", "help"]
/// let sorted_words = sort_by_similarity(words, "hep", None)
/// // sorted_words will be ["help", "hi", "hello"]
/// ```
pub fn compare_similarity(
given_word : String,
options : CompareSimilarityOptions?,
) -> (String, String) -> Int {
let opts = match options {
Some(opts) => opts
None => default_compare_similarity_options()
}
let compare_fn = opts.compare_fn
if opts.case_sensitive {
fn(a : String, b : String) -> Int {
compare_fn(given_word, a) - compare_fn(given_word, b)
}
} else {
let given_lower = to_lowercase_string(given_word)
fn(a : String, b : String) -> Int {
let a_lower = to_lowercase_string(a)
let b_lower = to_lowercase_string(b)
compare_fn(given_lower, a_lower) - compare_fn(given_lower, b_lower)
}
}
}
///|
/// Simplified version using default options
pub fn compare_similarity_simple(
given_word : String,
) -> (String, String) -> Int {
compare_similarity(given_word, None)
}
///|
/// Sort string array by similarity
///
/// * `words` - The string array to sort
/// * `given_word` - The reference string for similarity comparison
/// * `options` - Comparison options
/// + Returns a new array sorted by similarity (higher similarity first)
pub fn sort_by_similarity(
words : Array[String],
given_word : String,
options : CompareSimilarityOptions?,
) -> Array[String] {
let comparator = compare_similarity(given_word, options)
let sorted_words = Array::new()
for word in words {
sorted_words.push(word)
}
// Simple insertion sort
for i = 1; i < sorted_words.length(); i = i + 1 {
let key = sorted_words[i]
let mut j = i - 1
while j >= 0 && comparator(sorted_words[j], key) > 0 {
sorted_words[j + 1] = sorted_words[j]
j = j - 1
}
sorted_words[j + 1] = key
}
sorted_words
}
///|
/// Tests
test "compare_similarity_basic" {
let cmp = compare_similarity_simple("hep")
// "help" should be more similar to "hep" than "hello"
assert_true(cmp("help", "hello") < 0)
// "hi" should be more similar to "hep" than "hello"
assert_true(cmp("hi", "hello") < 0)
// Same strings should have distance 0
assert_eq(cmp("test", "test"), 0)
}
///|
test "compare_similarity_case_sensitive" {
let opts = case_sensitive_compare_similarity_options()
let cmp = compare_similarity("Test", Some(opts))
// When case sensitive, "Test" should be more similar to "Test" than "test"
assert_true(cmp("Test", "test") < 0)
}
///|
test "sort_by_similarity" {
let words = ["hi", "hello", "help"]
let sorted = sort_by_similarity(words, "hep", None)
// "help" should be most similar
assert_eq(sorted[0], "help")
// Original array should not be modified
assert_eq(words, ["hi", "hello", "help"])
}
///|
/// Helper function: assert boolean value is true
fn assert_true(value : Bool) -> Unit {
if not(value) {
abort("Expected true, got false")
}
}