String Similarity .NET

repository·main·Indexed 19 days ago

https://github.com/feature23/stringsimilarity.net

A .NET library ported from java-string-similarity providing various string similarity and distance measurement algorithms for fuzzy matching, OCR correction, and text comparison. It includes implementations of Levenshtein, Damerau-Levenshtein, Jaro-Winkler, Longest Common Subsequence (LCS), N-Gram, Q-Gram, Cosine similarity, and Ratcliff-Obershelp, categorized by similarity, distance, and metric distance interfaces.

Tokens
3.6K
Snippets
12
Records
15
Agent score
18%

What's inside F23.StringSimilarity

  1. Understand similarity and distance interfaces

    main

    The library uses specific interfaces to categorize algorithms based on their output and mathematical properties. Understanding these helps you choose the right algorithm for your use case:

    Similarity Interfaces

    • StringSimilarity: Algorithms where 0 means strings are completely different.
    • NormalizedStringSimilarity: Algorithms that return a similarity value between 0.0 and 1.0 (e.g., Jaro-Winkler).

    Distance Interfaces

    • StringDistance: Algorithms where 0 means strings are identical. The maximum distance depends on the algorithm.
    • NormalizedStringDistance: Algorithms where the distance is always between 0.0 and 1.0 (e.g., NormalizedLevenshtein).

    Metric Distances

    • MetricStringDistance: A subset of distances that satisfy the triangle inequality d(x, y) <= d(x, z) + d(z, y). This is a requirement for certain nearest-neighbor search algorithms and indexing structures. Note that while Levenshtein is a metric distance, NormalizedLevenshtein is not.
  2. Compute similarity using Shingle (n-gram) based algorithms

    main

    Shingle-based algorithms convert strings into sets or profiles of n-grams. There are two ways to use them:

    1. Directly compute distance: Use the Distance method on the algorithm object (e.g., QGram).
    2. Pre-compute profiles for large datasets: Use GetProfile(string) to create a profile, then use Similarity(profile1, profile2) to compare them.

    Important: When using profiles, you must use the same algorithm instance (e.g., the same Cosine object) to parse all input strings to ensure consistency.

    // Direct distance calculation
    var dig = new QGram(2);
    Console.WriteLine(dig.Distance("ABCD", "ABCE"));
    
    // Profile-based similarity for large datasets
    var cosine = new Cosine(2);
    var profile1 = cosine.GetProfile("My first string");
    var profile2 = cosine.GetProfile("My other string...");
    Console.WriteLine(cosine.Similarity(profile1, profile2));
  3. Use Weighted Levenshtein with custom substitution costs

    main

    The WeightedLevenshtein algorithm allows you to define custom costs for different character substitutions. This is useful for OCR applications (where certain characters look similar) or keyboard auto-correction (where certain characters are adjacent). You must implement the ICharacterSubstitution interface.

    using System;
    using F23.StringSimilarity;
    
    public class Program
    {
        public static void Main(string[] args)
        {
            var l = new WeightedLevenshtein(new ExampleCharSub());
    
            Console.WriteLine(l.Distance("String1", "String1"));
            Console.WriteLine(l.Distance("String1", "Srring1"));
            Console.WriteLine(l.Distance("String1", "Srring2"));
        }
    }
    
    private class ExampleCharSub : ICharacterSubstitution
    {
        public double Cost(char c1, char c2)
        {
            // The cost for substituting 't' and 'r' is considered smaller as these 2 are located next to each other on a keyboard
            if (c1 == 't' && c2 == 'r') return 0.5; 
    
            // For most cases, the cost of substituting 2 characters is 1.0
            return 1.0;
        }
    }
  4. Use Ratcliff-Obershelp similarity

    main

    The RatcliffObershelp class implements Gestalt Pattern Matching. It returns a similarity value in the interval [0.0, 1.0]. The distance is computed as 1 - Ratcliff/Obershelp similarity.

    using System;
    using F23.StringSimilarity;
    
    public class Program
    {
        public static void Main(string[] args)
        {
            var ro = new RatcliffObershelp();
            
            // substitution of s and t
            Console.WriteLine(ro.Similarity("My string", "My tsring"));
            
            // substitution of s and n
            Console.WriteLine(ro.Similarity("My string", "My ntrisg"));
        }
    }
  5. Use Levenshtein distance

    main

    The Levenshtein algorithm calculates the minimum number of single-character edits (insertions, deletions, or substitutions) required to change one word into another. It is a metric distance and uses dynamic programming with $O(m \cdot n)$ complexity.

    using System;
    using F23.StringSimilarity;
    
    public class Program
    {
        public static void Main(string[] args)
        {
            var l = new Levenshtein();
    
            Console.WriteLine(l.Distance("My string", "My $tring"));
        }
    }
  6. Use Q-Gram distance

    main
    The QGram class implements Q-gram distance, defined as the L1 norm of the difference of the profiles (the sum of the absolute differences of the occurrences of each n-gram). It is a lower bound on Levenshtein distance and is more efficient, with a complexity of $O(m + n)$.
  7. Use experimental SIFT4 distance

    main

    The Sift4 class is an experimental algorithm inspired by JaroWinkler and LCS. It aims to match human perception of string distance by considering substitution, character distance, and LCS. It includes a MaxOffset property to configure its behavior.

    using System;
    using System.Diagnostics;
    using F23.StringSimilarity;
    
    public class Program
    {
        public static void Main(string[] args)
        {
            var s1 = "This is the first string";
            var s2 = "And this is another string";
            var sift4 = new Sift4();
            sift4.MaxOffset = 5;
            double result = sift4.Distance(s1, s2);
            Debug.Assert(Math.Abs(result - 11.0) < 0.1);
        }
    }
  8. Use Metric Longest Common Subsequence

    main

    The MetricLCS class provides a distance metric based on the Longest Common Subsequence. The distance is computed as 1 - |LCS(s1, s2)| / max(|s1|, |s2|).

    using System;
    using F23.StringSimilarity;
    
    public class Program
    {
        public static void Main(string[] args)
        {
            var lcs = new MetricLCS();
    
            string s1 = "ABCDEFG";   
            string s2 = "ABCDEFHJKL";
            // LCS: ABCDEF => length = 6
            // longest = s2 => length = 10
            // => 1 - 6/10 = 0.4
            Console.WriteLine(lcs.Distance(s1, s2));
    
            Console.WriteLine(lcs.Distance("ABDEF", "ABDIF"));
        }
    }
  9. Use N-Gram distance

    main

    The NGram class implements normalized N-Gram distance. It uses affixing with the special character \n to increase the weight of the first characters. The normalization is achieved by dividing the total similarity score by the original length of the longest word. You must provide the n value (the size of the n-gram) in the constructor.

    using System;
    using F23.StringSimilarity;
    
    public class Program
    {
        public static void Main(string[] args)
        {
            // produces 0.583333
            var twogram = new NGram(2);
            Console.WriteLine(twogram.Distance("ABCD", "ABTUIO"));
            
            // produces 0.97222
            string s1 = "Adobe CreativeSuite 5 Master Collection from cheap 4zp";
            string s2 = "Adobe CreativeSuite 5 Master Collection from cheap d1x";
            var ngram = new NGram(4);
            Console.WriteLine(ngram.Distance(s1, s2));
        }
    }
  10. Use Longest Common Subsequence (LCS) distance

    main

    The LongestCommonSubsequence class finds the longest subsequence common to two sequences. Unlike substrings, subsequences do not need to be consecutive. The distance is calculated as n + m - 2 |LCS(X, Y)|, where n and m are the lengths of the strings. This implementation uses a dynamic programming approach with $O(m imes n)$ time and space complexity.

    using System;
    using F23.StringSimilarity;
    
    public class Program
    {
        public static void Main(string[] args)
        {
            var lcs = new LongestCommonSubsequence();
    
            // Will produce 4.0
            Console.WriteLine(lcs.Distance("AGCAT", "GAC"));
            
            // Will produce 1.0
            Console.WriteLine(lcs.Distance("AGCAT", "AGCT"));
        }
    }