using System;
namespace Algorithms.Strings.Similarity;
/// <summary>
/// <para>
/// Jaro Similarity measures how similar two strings are.
/// Result is between 0 and 1 where 0 represnts that there is no similarity between strings and 1 represents equal strings.
/// Time complexity is O(a*b) where a is the length of the first string and b is the length of the second string.
/// </para>
/// <para>
/// Wikipedia: https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance#Jaro_similarity.
/// </para>
/// </summary>
public static class JaroSimilarity
{
/// <summary>
/// Calculates Jaro Similarity between two strings.
/// </summary>
/// <param name="s1">First string.</param>
/// <param name="s2">Second string.</param>
public static double Calculate(string s1, string s2)
{
if (s1 == s2)
{
return 1;
}
var longerString = s1.Length > s2.Length ? s1 : s2;
var shorterString = s1.Length < s2.Length ? s1 : s2;
// will look for matching characters in this range
var matchingCharacterRange = Math.Max((longerString.Length / 2) - 1, 0);
var matches = 0d;
// true if i-th index of s1 was matched in s2
var s1MatchedIndeces = new bool[s1.Length];
// true if i-th index of s2 was matched in s1
var s2MatchedIndeces = new bool[s2.Length];
for (var i = 0; i < longerString.Length; i++)
{
var startIndex = Math.Max(i - matchingCharacterRange, 0);
var endIndex = Math.Min(i + matchingCharacterRange, shorterString.Length - 1);
for (var j = startIndex; j <= endIndex; j++)
{
if (s1[i] == s2[j] && !s2MatchedIndeces[j])
{
matches++;
s1MatchedIndeces[i] = true;
s2MatchedIndeces[j] = true;
break;
}
}
}
if (matches == 0)
{
return 0;
}
var transpositions = CalculateTranspositions(s1, s2, s1MatchedIndeces, s2MatchedIndeces);
return ((matches / s1.Length) + (matches / s2.Length) + ((matches - transpositions) / matches)) / 3;
}
/// <summary>
/// Calculates number of matched characters that are not in the right order.
/// </summary>
private static int CalculateTranspositions(string s1, string s2, bool[] s1MatchedIndeces, bool[] s2MatchedIndeces)
{
var transpositions = 0;
var s2Index = 0;
for (var s1Index = 0; s1Index < s1.Length; s1Index++)
{
if (s1MatchedIndeces[s1Index])
{
while (!s2MatchedIndeces[s2Index])
{
s2Index++;
}
if (s1[s1Index] != s2[s2Index])
{
transpositions++;
}
s2Index++;
}
}
transpositions /= 2;
return transpositions;
}
}