diff --git a/src/NzbDrone.Common.Test/LevenshteinDistanceFixture.cs b/src/NzbDrone.Common.Test/LevenshteinDistanceFixture.cs new file mode 100644 index 000000000..27fe63480 --- /dev/null +++ b/src/NzbDrone.Common.Test/LevenshteinDistanceFixture.cs @@ -0,0 +1,50 @@ +using System; +using System.Diagnostics; +using System.IO; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.EnvironmentInfo; +using NzbDrone.Test.Common; + +namespace NzbDrone.Common.Test +{ + [TestFixture] + public class LevenshteinDistanceFixture : TestBase + { + [TestCase("", "", 0)] + [TestCase("abc", "abc", 0)] + [TestCase("abc", "abcd", 1)] + [TestCase("abcd", "abc", 1)] + [TestCase("abc", "abd", 1)] + [TestCase("abc", "adc", 1)] + [TestCase("abcdefgh", "abcghdef", 4)] + [TestCase("a.b.c.", "abc", 3)] + [TestCase("Agents Of SHIELD", "Marvel's Agents Of S.H.I.E.L.D.", 15)] + [TestCase("Agents of cracked", "Agents of shield", 6)] + [TestCase("ABCxxx", "ABC1xx", 1)] + [TestCase("ABC1xx", "ABCxxx", 1)] + public void LevenshteinDistance(String text, String other, Int32 expected) + { + text.LevenshteinDistance(other).Should().Be(expected); + } + + [TestCase("", "", 0)] + [TestCase("abc", "abc", 0)] + [TestCase("abc", "abcd", 1)] + [TestCase("abcd", "abc", 3)] + [TestCase("abc", "abd", 3)] + [TestCase("abc", "adc", 3)] + [TestCase("abcdefgh", "abcghdef", 8)] + [TestCase("a.b.c.", "abc", 0)] + [TestCase("Agents of shield", "Marvel's Agents Of S.H.I.E.L.D.", 9)] + [TestCase("Agents of shield", "Agents of cracked", 14)] + [TestCase("Agents of shield", "the shield", 24)] + [TestCase("ABCxxx", "ABC1xx", 3)] + [TestCase("ABC1xx", "ABCxxx", 3)] + public void LevenshteinDistanceClean(String text, String other, Int32 expected) + { + text.ToLower().LevenshteinDistanceClean(other.ToLower()).Should().Be(expected); + } + } +} diff --git a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj index ae5e48bf8..e7f6a681f 100644 --- a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj +++ b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj @@ -67,6 +67,7 @@ + diff --git a/src/NzbDrone.Common/LevenstheinExtensions.cs b/src/NzbDrone.Common/LevenstheinExtensions.cs new file mode 100644 index 000000000..3bc54d5b2 --- /dev/null +++ b/src/NzbDrone.Common/LevenstheinExtensions.cs @@ -0,0 +1,55 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using ICSharpCode.SharpZipLib.Zip; + +namespace NzbDrone.Common +{ + public static class LevenstheinExtensions + { + public static Int32 LevenshteinDistance(this String text, String other, Int32 costInsert = 1, Int32 costDelete = 1, Int32 costSubstitute = 1) + { + if (text == other) return 0; + if (text.Length == 0) return other.Length * costInsert; + if (other.Length == 0) return text.Length * costDelete; + + Int32[] matrix = new Int32[other.Length + 1]; + + for (var i = 1; i < matrix.Length; i++) + { + matrix[i] = i * costInsert; + } + + for (var i = 0; i < text.Length; i++) + { + Int32 topLeft = matrix[0]; + matrix[0] = matrix[0] + costDelete; + + for (var j = 0; j < other.Length; j++) + { + Int32 top = matrix[j]; + Int32 left = matrix[j + 1]; + + var sumIns = top + costInsert; + var sumDel = left + costDelete; + var sumSub = topLeft + (text[i] == other[j] ? 0 : costSubstitute); + + topLeft = matrix[j + 1]; + matrix[j + 1] = Math.Min(Math.Min(sumIns, sumDel), sumSub); + } + } + + return matrix[other.Length]; + } + + public static Int32 LevenshteinDistanceClean(this String expected, String other) + { + expected = expected.ToLower().Replace(".", ""); + other = other.ToLower().Replace(".", ""); + + return expected.LevenshteinDistance(other, 1, 3, 3); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index c75eeecc3..bab538ec7 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -114,6 +114,7 @@ + diff --git a/src/NzbDrone.Core/MetadataSource/TraktProxy.cs b/src/NzbDrone.Core/MetadataSource/TraktProxy.cs index 664778b63..22e11bec1 100644 --- a/src/NzbDrone.Core/MetadataSource/TraktProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/TraktProxy.cs @@ -35,7 +35,9 @@ namespace NzbDrone.Core.MetadataSource var restRequest = new RestRequest(GetSearchTerm(title) + "/30/seasons"); var response = client.ExecuteAndValidate>(restRequest); - return response.Select(MapSeries).ToList(); + return response.Select(MapSeries) + .OrderBy(v => title.LevenshteinDistanceClean(v.Title)) + .ToList(); } catch (WebException ex) {