diff --git a/Algorithms.Tests/LinearAlgebra/Distances/MinkowskiTests.cs b/Algorithms.Tests/LinearAlgebra/Distances/MinkowskiTests.cs new file mode 100644 index 00000000..193677b7 --- /dev/null +++ b/Algorithms.Tests/LinearAlgebra/Distances/MinkowskiTests.cs @@ -0,0 +1,28 @@ +using NUnit.Framework; +using Algorithms.LinearAlgebra.Distances; +using FluentAssertions; +using System; + +namespace Algorithms.Tests.LinearAlgebra.Distances; + +public class MinkowskiTests +{ + [TestCase(new[] { 2.0, 3.0 }, new[] { -1.0, 5.0 }, 1, 5.0)] // Simulate Manhattan condition + [TestCase(new[] { 7.0, 4.0, 3.0 }, new[] { 17.0, 6.0, 2.0 }, 2, 10.247)] // Simulate Euclidean condition + [TestCase(new[] { 1.0, 2.0, 3.0, 4.0 }, new[] { 1.75, 2.25, -3.0, 0.5 }, 20, 6.0)] // Simulate Chebyshev condition + [TestCase(new[] { 1.0, 1.0, 9.0 }, new[] { 2.0, 2.0, -5.2 }, 3, 14.2)] + [TestCase(new[] { 1.0, 2.0, 3.0 }, new[] { 1.0, 2.0, 3.0 }, 5, 0.0)] + public void DistanceTest(double[] point1, double[] point2, int order, double expectedDistance) + { + Minkowski.Distance(point1, point2, order).Should().BeApproximately(expectedDistance, 0.01); + } + + [TestCase(new[] { 2.0, 3.0 }, new[] { -1.0 }, 2)] + [TestCase(new[] { 1.0 }, new[] { 1.0, 2.0, 3.0 }, 1)] + [TestCase(new[] { 1.0, 1.0 }, new[] { 2.0, 2.0 }, 0)] + public void DistanceThrowsArgumentExceptionOnInvalidInput(double[] point1, double[] point2, int order) + { + Action action = () => Minkowski.Distance(point1, point2, order); + action.Should().Throw(); + } +} \ No newline at end of file diff --git a/Algorithms/LinearAlgebra/Distances/Minkowski.cs b/Algorithms/LinearAlgebra/Distances/Minkowski.cs new file mode 100644 index 00000000..9b3080f3 --- /dev/null +++ b/Algorithms/LinearAlgebra/Distances/Minkowski.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; + +namespace Algorithms.LinearAlgebra.Distances; + +/// +/// Implementation of Minkowski distance. +/// It is the sum of the lengths of the projections of the line segment between the points onto the +/// coordinate axes, raised to the power of the order and then taking the p-th root. +/// For the case of order = 1, the Minkowski distance degenerates to the Manhattan distance, +/// for order = 2, the usual Euclidean distance is obtained and for order = infinity, the Chebyshev distance is obtained. +/// +public static class Minkowski +{ + /// + /// Calculate Minkowski distance for two N-Dimensional points. + /// + /// First N-Dimensional point. + /// Second N-Dimensional point. + /// Order of the Minkowski distance. + /// Calculated Minkowski distance. + public static double Distance(double[] point1, double[] point2, int order) + { + if (order < 1) + { + throw new ArgumentException("The order must be greater than or equal to 1."); + } + + if (point1.Length != point2.Length) + { + throw new ArgumentException("Both points should have the same dimensionality"); + } + + // distance = (|x1-y1|^p + |x2-y2|^p + ... + |xn-yn|^p)^(1/p) + return Math.Pow(point1.Zip(point2, (x1, x2) => Math.Pow(Math.Abs(x1 - x2), order)).Sum(), 1.0 / order); + } +} diff --git a/README.md b/README.md index 6d4e1e93..c9fd190e 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ find more than one implementation for the same objective but using different alg * [Chebyshev](./Algorithms/LinearAlgebra/Distances/Chebyshev.cs) * [Euclidean](./Algorithms/LinearAlgebra/Distances/Euclidean.cs) * [Manhattan](./Algorithms/LinearAlgebra/Distances/Manhattan.cs) + * [Minkowski](./Algorithms/LinearAlgebra/Distances/Minkowski.cs) * [Eigenvalue](./Algorithms/LinearAlgebra/Eigenvalue) * [Power Iteration](./Algorithms/LinearAlgebra/Eigenvalue/PowerIteration.cs) * [Modular Arithmetic](./Algorithms/ModularArithmetic)