-
Notifications
You must be signed in to change notification settings - Fork 21
Implement ChapmanMeanLengthTrue #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,11 @@ | |||||
|
|
||||||
| namespace SimMetrics.Net.Metric | ||||||
| { | ||||||
| /// <summary> | ||||||
| /// This method only the lengths of the two words, not at the actual characters. | ||||||
| /// It uses some cutoff(ChapmanMeanLengthMaxString) and a polynomial scaling(1 - num2^4) to produce a score. | ||||||
|
||||||
| /// It uses some cutoff(ChapmanMeanLengthMaxString) and a polynomial scaling(1 - num2^4) to produce a score. | |
| /// It uses some cutoff (ChapmanMeanLengthMaxString) and a polynomial scaling (1 - num2^4) to produce a score. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,76 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using System; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using SimMetrics.Net.API; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace SimMetrics.Net.Metric; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Correct Chapman Mean Length implementation. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Correct Chapman Mean Length implementation. | |
| /// Implements the Chapman Mean Length similarity algorithm, which measures the similarity between two strings | |
| /// based on the length of their longest common subsequence (LCS), normalized by the mean length of the input strings. | |
| /// This implementation is considered "correct" as it strictly follows the original Chapman Mean Length definition, | |
| /// addressing inaccuracies or deviations present in the existing <see cref="ChapmanMeanLength"/> class. | |
| /// The similarity score is calculated as 2 * LCS(firstWord, secondWord) / (|firstWord| + |secondWord|). | |
| /// For more details, see: Chapman, S. (1995). "String similarity metrics for information retrieval". |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition string.IsNullOrEmpty(firstWord) || string.IsNullOrEmpty(secondWord) returns 0.0 even when both strings are empty. Two empty strings should arguably return 1.0 (perfect match) since they are identical. Consider checking if (firstWord == null || secondWord == null) return DefaultMismatchScore; followed by a separate check for the case when both strings are empty: if (firstWord.Length == 0 && secondWord.Length == 0) return DefaultPerfectScore;
| if (string.IsNullOrEmpty(firstWord) || string.IsNullOrEmpty(secondWord)) | |
| { | |
| return DefaultMismatchScore; | |
| } | |
| if (firstWord == null || secondWord == null) | |
| { | |
| return DefaultMismatchScore; | |
| } | |
| if (firstWord.Length == 0 && secondWord.Length == 0) | |
| { | |
| return DefaultPerfectScore; | |
| } | |
| if (firstWord.Length == 0 || secondWord.Length == 0) | |
| { | |
| return DefaultMismatchScore; | |
| } |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The GetSimilarityTimingEstimated method returns a constant 0.0, but this metric has O(m*n) time complexity due to the LCS algorithm. Consider implementing a proper timing estimate like firstWord.Length * secondWord.Length * estimatedTimingConstant (see Levenstein implementation for reference).
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both branches of this 'if' statement write to the same variable - consider using '?' to express intent better.
| if (s1[i] == s2[j]) | |
| { | |
| dp[i + 1, j + 1] = dp[i, j] + 1; | |
| } | |
| else | |
| { | |
| dp[i + 1, j + 1] = Math.Max(dp[i, j + 1], dp[i + 1, j]); | |
| } | |
| dp[i + 1, j + 1] = (s1[i] == s2[j]) | |
| ? dp[i, j] + 1 | |
| : Math.Max(dp[i, j + 1], dp[i + 1, j]); |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file uses file-scoped namespace declaration (with semicolon), while all other files in the Metric directory use block-scoped namespace declarations (with braces). Consider using block-scoped namespaces for consistency with the existing codebase.
| namespace SimMetrics.Net.Metric; | |
| /// <summary> | |
| /// Correct Chapman Mean Length implementation. | |
| /// </summary> | |
| public sealed class ChapmanMeanLengthTrue : AbstractStringMetric | |
| { | |
| private const double DefaultMismatchScore = 0.0; | |
| private const double DefaultPerfectScore = 1.0; | |
| public override double GetSimilarity(string firstWord, string secondWord) | |
| { | |
| if (string.IsNullOrEmpty(firstWord) || string.IsNullOrEmpty(secondWord)) | |
| { | |
| return DefaultMismatchScore; | |
| } | |
| // Compute LCS length | |
| var lcs = LongestCommonSubsequence(firstWord, secondWord); | |
| // Chapman Mean Length formula | |
| var score = 2.0 * lcs / (firstWord.Length + secondWord.Length); | |
| return score switch | |
| { | |
| < DefaultMismatchScore => DefaultMismatchScore, | |
| > DefaultPerfectScore => DefaultPerfectScore, | |
| _ => score | |
| }; | |
| } | |
| public override string GetSimilarityExplained(string firstWord, string secondWord) | |
| { | |
| throw new NotImplementedException(); | |
| } | |
| public override double GetSimilarityTimingEstimated(string firstWord, string secondWord) | |
| { | |
| return 0.0; | |
| } | |
| public override double GetUnnormalisedSimilarity(string firstWord, string secondWord) | |
| { | |
| return GetSimilarity(firstWord, secondWord); | |
| } | |
| public override string LongDescriptionString => "A true implementation of the Chapman Mean Length algorithm"; | |
| public override string ShortDescriptionString => nameof(ChapmanMeanLengthTrue); | |
| private static int LongestCommonSubsequence(string s1, string s2) | |
| { | |
| int m = s1.Length, n = s2.Length; | |
| int[,] dp = new int[m + 1, n + 1]; | |
| for (var i = 0; i < m; i++) | |
| { | |
| for (var j = 0; j < n; j++) | |
| { | |
| if (s1[i] == s2[j]) | |
| { | |
| dp[i + 1, j + 1] = dp[i, j] + 1; | |
| } | |
| else | |
| { | |
| dp[i + 1, j + 1] = Math.Max(dp[i, j + 1], dp[i + 1, j]); | |
| } | |
| } | |
| } | |
| return dp[m, n]; | |
| namespace SimMetrics.Net.Metric | |
| { | |
| /// <summary> | |
| /// Correct Chapman Mean Length implementation. | |
| /// </summary> | |
| public sealed class ChapmanMeanLengthTrue : AbstractStringMetric | |
| { | |
| private const double DefaultMismatchScore = 0.0; | |
| private const double DefaultPerfectScore = 1.0; | |
| public override double GetSimilarity(string firstWord, string secondWord) | |
| { | |
| if (string.IsNullOrEmpty(firstWord) || string.IsNullOrEmpty(secondWord)) | |
| { | |
| return DefaultMismatchScore; | |
| } | |
| // Compute LCS length | |
| var lcs = LongestCommonSubsequence(firstWord, secondWord); | |
| // Chapman Mean Length formula | |
| var score = 2.0 * lcs / (firstWord.Length + secondWord.Length); | |
| return score switch | |
| { | |
| < DefaultMismatchScore => DefaultMismatchScore, | |
| > DefaultPerfectScore => DefaultPerfectScore, | |
| _ => score | |
| }; | |
| } | |
| public override string GetSimilarityExplained(string firstWord, string secondWord) | |
| { | |
| throw new NotImplementedException(); | |
| } | |
| public override double GetSimilarityTimingEstimated(string firstWord, string secondWord) | |
| { | |
| return 0.0; | |
| } | |
| public override double GetUnnormalisedSimilarity(string firstWord, string secondWord) | |
| { | |
| return GetSimilarity(firstWord, secondWord); | |
| } | |
| public override string LongDescriptionString => "A true implementation of the Chapman Mean Length algorithm"; | |
| public override string ShortDescriptionString => nameof(ChapmanMeanLengthTrue); | |
| private static int LongestCommonSubsequence(string s1, string s2) | |
| { | |
| int m = s1.Length, n = s2.Length; | |
| int[,] dp = new int[m + 1, n + 1]; | |
| for (var i = 0; i < m; i++) | |
| { | |
| for (var j = 0; j < n; j++) | |
| { | |
| if (s1[i] == s2[j]) | |
| { | |
| dp[i + 1, j + 1] = dp[i, j] + 1; | |
| } | |
| else | |
| { | |
| dp[i + 1, j + 1] = Math.Max(dp[i, j + 1], dp[i + 1, j]); | |
| } | |
| } | |
| } | |
| return dp[m, n]; | |
| } |
Copilot
AI
Dec 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The LCS implementation uses O(m*n) space complexity. For large strings, consider optimizing to use O(min(m, n)) space by only keeping the previous and current rows of the DP table, since you only need the final result and not the actual LCS itself.
| int[,] dp = new int[m + 1, n + 1]; | |
| for (var i = 0; i < m; i++) | |
| { | |
| for (var j = 0; j < n; j++) | |
| { | |
| if (s1[i] == s2[j]) | |
| { | |
| dp[i + 1, j + 1] = dp[i, j] + 1; | |
| } | |
| else | |
| { | |
| dp[i + 1, j + 1] = Math.Max(dp[i, j + 1], dp[i + 1, j]); | |
| } | |
| } | |
| } | |
| return dp[m, n]; | |
| // Ensure s2 is the shorter string to minimize space | |
| if (n > m) | |
| { | |
| // Swap to always use less space | |
| var temp = s1; s1 = s2; s2 = temp; | |
| m = s1.Length; n = s2.Length; | |
| } | |
| int[] prev = new int[n + 1]; | |
| int[] curr = new int[n + 1]; | |
| for (int i = 0; i < m; i++) | |
| { | |
| for (int j = 0; j < n; j++) | |
| { | |
| if (s1[i] == s2[j]) | |
| { | |
| curr[j + 1] = prev[j] + 1; | |
| } | |
| else | |
| { | |
| curr[j + 1] = Math.Max(prev[j + 1], curr[j]); | |
| } | |
| } | |
| // Swap rows for next iteration | |
| var tempArr = prev; prev = curr; curr = tempArr; | |
| } | |
| return prev[n]; |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |||||
| <VersionPrefix>1.0.5.0</VersionPrefix> | ||||||
| <Authors>Hamed Fathi;Stef Heyenrath</Authors> | ||||||
| <TargetFrameworks>net20;net35;net40;net45;netstandard1.0;netstandard2.0</TargetFrameworks> | ||||||
| <LangVersion>12</LangVersion> | ||||||
|
||||||
| <LangVersion>12</LangVersion> | |
| <LangVersion>2</LangVersion> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,16 @@ | ||
| using Xunit; | ||
|
|
||
| namespace SimMetrics.Net.Tests | ||
| namespace SimMetrics.Net.Tests; | ||
|
|
||
| internal static class AssertUtil | ||
| { | ||
| internal static class AssertUtil | ||
| public static void Equal<T>(T expected, T actual) | ||
| { | ||
| public static void Equal<T>(T expected, T actual) | ||
| { | ||
| Assert.Equal(expected, actual); | ||
| } | ||
| Assert.Equal(expected, actual); | ||
| } | ||
|
|
||
| public static void Equal<T>(T expected, T actual, string message) | ||
| { | ||
| Assert.True(expected.Equals(actual), message); | ||
| } | ||
| public static void Equal<T>(T expected, T actual, string message) | ||
| { | ||
| Assert.True(expected.Equals(actual), message); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| using SimMetrics.Net.Metric; | ||
| using Xunit; | ||
|
|
||
| namespace SimMetrics.Net.Tests.SimilarityClasses.LengthBased; | ||
|
||
|
|
||
| public sealed class ChapmanMeanLengthTrueTests | ||
| { | ||
| private readonly ChapmanMeanLengthTrue _sut = new(); | ||
|
|
||
| [Theory] | ||
| [InlineData("Davdi", 0.800000)] | ||
| [InlineData("david", 0.800000)] | ||
| [InlineData("David", 1.000000)] | ||
| [InlineData("Maday", 0.400000)] | ||
| [InlineData("Daves", 0.600000)] | ||
| [InlineData("divaD", 0.200000)] | ||
| [InlineData("Dave", 0.666667)] | ||
| [InlineData("Dovid", 0.800000)] | ||
| [InlineData("Dadiv", 0.600000)] | ||
| [InlineData("Da.v.id", 0.833333)] | ||
| [InlineData("Dav id", 0.909091)] | ||
| [InlineData("12345", 0.000000)] | ||
| [InlineData("Divad", 0.600000)] | ||
| [InlineData("D-avid", 0.909091)] | ||
| [InlineData("xxxxx", 0.000000)] | ||
|
Comment on lines
+10
to
+25
|
||
| public void GetSimilarity(string test, double expected) | ||
| { | ||
| var result = _sut.GetSimilarity("David", test); | ||
|
|
||
| Assert.Equal(expected, result, 5); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Grammar issue: The sentence is missing a verb. Should be "This method looks at only the lengths of the two words" or "This method uses only the lengths of the two words".