diff --git a/DropoutCoder.PolylineAlgorithm.sln b/DropoutCoder.PolylineAlgorithm.sln
index d4eca94d..a7673087 100644
--- a/DropoutCoder.PolylineAlgorithm.sln
+++ b/DropoutCoder.PolylineAlgorithm.sln
@@ -10,13 +10,19 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{576FEFFC
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{7F9807A1-01FF-40C3-9342-3F3F35D632DA}"
ProjectSection(SolutionItems) = preProject
- .\nuget\DropoutCoder.PolylineAlgorithm.nuspec = .\nuget\DropoutCoder.PolylineAlgorithm.nuspec
+ nuget\DropoutCoder.PolylineAlgorithm.nuspec = nuget\DropoutCoder.PolylineAlgorithm.nuspec
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DropoutCoder.PolylineAlgorithm.Tests", "tests\DropoutCoder.PolylineAlgorithm.Tests.csproj", "{30324A08-AA42-425D-87DA-8F9C6AF60454}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{C13E31F9-B8EF-4915-A1C8-BC33F6431EB9}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{33C03F16-4313-4579-87E6-65892AF21D7D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DropoutCoder.PolylineAlgorithm.Benchmarks", "benchmarks\DropoutCoder.PolylineAlgorithm.Benchmarks\DropoutCoder.PolylineAlgorithm.Benchmarks.csproj", "{9C7CBAD5-415B-4589-86E1-01C849F9C56C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks", "benchmarks\DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks\DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj", "{D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -31,6 +37,14 @@ Global
{30324A08-AA42-425D-87DA-8F9C6AF60454}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30324A08-AA42-425D-87DA-8F9C6AF60454}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30324A08-AA42-425D-87DA-8F9C6AF60454}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9C7CBAD5-415B-4589-86E1-01C849F9C56C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9C7CBAD5-415B-4589-86E1-01C849F9C56C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9C7CBAD5-415B-4589-86E1-01C849F9C56C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9C7CBAD5-415B-4589-86E1-01C849F9C56C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -38,6 +52,8 @@ Global
GlobalSection(NestedProjects) = preSolution
{882322A6-E758-4662-8D1C-7C555C8FC3F2} = {51C886AF-D610-48A4-9D73-2DEB38742801}
{30324A08-AA42-425D-87DA-8F9C6AF60454} = {576FEFFC-B624-40C3-A8AF-4E5233802EA0}
+ {9C7CBAD5-415B-4589-86E1-01C849F9C56C} = {33C03F16-4313-4579-87E6-65892AF21D7D}
+ {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B} = {33C03F16-4313-4579-87E6-65892AF21D7D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {93A268DC-0947-4FBB-B495-DDAD4B013D82}
diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks.csproj b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks.csproj
new file mode 100644
index 00000000..2d5b8cc1
--- /dev/null
+++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs
new file mode 100644
index 00000000..798fd3ae
--- /dev/null
+++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs
@@ -0,0 +1,40 @@
+namespace DropoutCoder.PolylineAlgorithm.Benchmarks
+{
+ using BenchmarkDotNet.Attributes;
+ using BenchmarkDotNet.Engines;
+ using DropoutCoder.PolylineAlgorithm.Encoding;
+
+ [MemoryDiagnoser]
+ [MarkdownExporter]
+ public class PolylineEncodingBenchmark
+ {
+ private Consumer _consumer = new Consumer();
+
+ [Params(10_000, 100_000, 1_000_000, Priority = 2)]
+ public int N;
+
+ public IEnumerable<(double, double)> Coordinates;
+
+ public PolylineEncoding Encoding { get; private set; }
+
+ public string Polyline;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ Encoding = new PolylineEncoding();
+ Coordinates = new[] { (42.88895, -100.30630), (44.91513, 19.22495), (20.40244, 7.97495), (-15.52130, -63.74380), (-78.95116, -72.18130), (38.63072, 88.13120), (60.81071, 151.41245), (-58.20769, -173.43130), (59.40939, 83.91245), (-58.20769, 61.41245), (-20.86278, -119.99380), (34.10374, -150.93130), (-71.15367, 31.88120), (-72.04138, -153.74380), (-49.99635, -107.33755), (76.12614, 135.94370), (70.05664, 41.72495), (63.43879, -77.80630), (13.68456, -90.46255), (-75.90519, -7.49380), (74.71112, -127.02505), (-66.61109, 17.81870), (-49.08384, 37.50620) };
+ Polyline = "}vwdGjafcRsvjKi}pxUhsrtCngtcAjjgzEdqvtLrscbKj}nr@wetlUc`nq]}_kfCyrfaK~wluUl`u}|@wa{lUmmuap@va{lU~oihCu||bF`|era@wsnnIjny{DxamaScqxza@dklDf{}kb@mtpeCavfzGqhx`Wyzzkm@jm`d@dba~Pppkg@h}pxU|rtnHp|flA|~xaPuykyN}fhv[h}pxUx~p}Ymx`sZih~iB{edwB";
+ }
+
+ [Benchmark]
+ public void Decode() => Encoding
+ .Decode(Polyline)
+ .Consume(_consumer);
+
+ [Benchmark]
+ public void Encode() => Encoding
+ .Encode(Coordinates)
+ .Consume(_consumer);
+ }
+}
diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/Program.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/Program.cs
new file mode 100644
index 00000000..d6e3655e
--- /dev/null
+++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/Program.cs
@@ -0,0 +1,13 @@
+namespace DropoutCoder.PolylineAlgorithm.Benchmarks
+{
+ using BenchmarkDotNet.Running;
+
+ internal class Program
+ {
+ static void Main(string[] args)
+ {
+ BenchmarkRunner
+ .Run();
+ }
+ }
+}
diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Constants.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Constants.cs
new file mode 100644
index 00000000..9854d042
--- /dev/null
+++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Constants.cs
@@ -0,0 +1,82 @@
+//
+// Copyright (c) Petr Šrámek. All rights reserved.
+// Licensed under the MIT License. See LICENSE file in the project root for full license information.
+//
+
+namespace DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks
+{
+ ///
+ /// Defines global constant values
+ ///
+ internal static class Constants
+ {
+ #region Constants
+
+ ///
+ /// Defines the coordinate precision
+ ///
+ public const double Precision = 1E5;
+
+ ///
+ /// Defines the shift length
+ ///
+ public const int ShiftLength = 5;
+
+ #endregion
+
+ ///
+ /// Defines ASCII characters constant values
+ ///
+ internal static class ASCII
+ {
+ #region Constants
+
+ ///
+ /// Defines the ASCII Question Mark
+ ///
+ public const int QuestionMark = 63;
+
+ ///
+ /// Defines the ASCII Space
+ ///
+ public const int Space = 32;
+
+ ///
+ /// Defines the ASCII Unit Separator
+ ///
+ public const int UnitSeparator = 31;
+
+ #endregion
+ }
+
+ ///
+ /// Defines coordinates constant values
+ ///
+ internal static class Coordinate
+ {
+ #region Constants
+
+ ///
+ /// Defines the maximum value for latitude
+ ///
+ public const int MaxLatitude = 90;
+
+ ///
+ /// Defines the maximum value for longitude
+ ///
+ public const int MaxLongitude = 180;
+
+ ///
+ /// Defines the minimum value for latitude
+ ///
+ public const int MinLatitude = -MaxLatitude;
+
+ ///
+ /// Defines the minimum value for longitude
+ ///
+ public const int MinLongitude = -MaxLongitude;
+
+ #endregion
+ }
+ }
+}
diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DecodePerformanceBenchmark.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DecodePerformanceBenchmark.cs
new file mode 100644
index 00000000..fd4ca47b
--- /dev/null
+++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DecodePerformanceBenchmark.cs
@@ -0,0 +1,200 @@
+namespace DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks
+{
+ using BenchmarkDotNet.Attributes;
+ using BenchmarkDotNet.Engines;
+
+ [MemoryDiagnoser]
+ public class DecodePerformanceBenchmark
+ {
+ private Consumer _consumer = new Consumer();
+
+ public static IEnumerable<(int, char[])> Polylines()
+ {
+ yield return (1, "mz}lHssngJj`gqSnx~lEcovfTnms{Zdy~qQj_deI".ToCharArray());
+ yield return (2, "}vwdGjafcRsvjKi}pxUhsrtCngtcAjjgzEdqvtLrscbKj}nr@wetlUc`nq]}_kfCyrfaK~wluUl`u}|@wa{lUmmuap@va{lU~oihCu||bF`|era@wsnnIjny{DxamaScqxza@dklDf{}kb@mtpeCavfzGqhx`Wyzzkm@jm`d@dba~Pppkg@h}pxU|rtnHp|flA|~xaPuykyN}fhv[h}pxUx~p}Ymx`sZih~iB{edwB".ToCharArray());
+ yield return (3, "}adrJh}}cVazlw@uykyNhaqeE`vfzG_~kY}~`eTsr{~Cwn~aOty_g@thapJvvoqKxt{sStfahDmtvmIfmiqBhjq|HujpgComs{Z}dhdKcidPymnvBqmquE~qrfI`x{lPf|ftGn~}d_@q}saAurjmu@bwr_DxrfaK~{rO~bidPwfduXwlioFlpum@twvfFpmi~VzxcsOqyejYhh|i@pbnr[twvfF_ueUujvbSa_d~ZkcnjZla~f[pmquEebxo[j}nr@xnn|H{gyiKbh{yH`oenn@y{mpIrbd~EmipgH}fuov@hjqtTp|flAttvkFrym_d@|eyCwn~aOfvdNmeawM??{yxdUcidPca{}D_atqGenzcAlra{@trgWhn{aZ??tluqOgu~sH".ToCharArray());
+ }
+
+ [Benchmark(Baseline = true)]
+ [ArgumentsSource(nameof(Polylines))]
+ public void Decode_V1((int, char[]) arg) => V1.Decode(arg.Item2).Consume(_consumer);
+
+ [Benchmark]
+ [ArgumentsSource(nameof(Polylines))]
+ public void Decode_V2((int, char[]) arg) => V2.Decode(arg.Item2).Consume(_consumer);
+
+ [Benchmark]
+ [ArgumentsSource(nameof(Polylines))]
+ public void Decode_V1_Parallel((int, char[]) arg) => Parallel.For(100, 200, (i) => V1.Decode(arg.Item2).Consume(_consumer));
+
+ [Benchmark]
+ [ArgumentsSource(nameof(Polylines))]
+ public void Decode_V2_Parallel((int, char[]) arg) => Parallel.For(100, 200, (i) => V2.Decode(arg.Item2).Consume(_consumer));
+
+ private class V1
+ {
+ public static IEnumerable<(double Latitude, double Longitude)> Decode(char[] polyline)
+ {
+ if (polyline is null || polyline.Length == 0)
+ {
+ throw new ArgumentException(nameof(polyline));
+ }
+
+ int index = 0;
+ int latitude = 0;
+ int longitude = 0;
+
+ var result = new List<(double Latitude, double Longitude)>();
+
+ while (index < polyline.Length)
+ {
+ if (!TryCalculateNext(ref polyline, ref index, ref latitude))
+ {
+ throw new InvalidOperationException();
+ }
+
+ if (!TryCalculateNext(ref polyline, ref index, ref longitude))
+ {
+ throw new InvalidOperationException();
+ }
+
+ var coordinate = (GetDoubleRepresentation(latitude), GetDoubleRepresentation(longitude));
+
+ if (!CoordinateValidator.IsValid(coordinate))
+ {
+ throw new InvalidOperationException();
+ }
+
+ result.Add(coordinate);
+ }
+
+ return result;
+ }
+
+ private static bool TryCalculateNext(ref char[] polyline, ref int index, ref int value)
+ {
+ int chunk;
+ int sum = 0;
+ int shifter = 0;
+
+ do
+ {
+ chunk = polyline[index++] - Constants.ASCII.QuestionMark;
+ sum |= (chunk & Constants.ASCII.UnitSeparator) << shifter;
+ shifter += Constants.ShiftLength;
+ } while (chunk >= Constants.ASCII.Space && index < polyline.Length);
+
+ if (index >= polyline.Length && chunk >= Constants.ASCII.Space)
+ return false;
+
+ value += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1;
+
+ return true;
+ }
+
+ private static double GetDoubleRepresentation(int value)
+ {
+ return Convert.ToDouble(value) / Constants.Precision;
+ }
+
+ public static class CoordinateValidator
+ {
+ public static bool IsValid((double Latitude, double Longitude) coordinate)
+ {
+ return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude);
+ }
+
+ public static bool IsValidLatitude(double latitude)
+ {
+ return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude;
+ }
+
+ public static bool IsValidLongitude(double longitude)
+ {
+ return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude;
+ }
+ }
+ }
+
+ private class V2
+ {
+ public static IEnumerable<(double Latitude, double Longitude)> Decode(char[] polyline)
+ {
+ if (polyline is null || polyline.Length == 0)
+ {
+ throw new ArgumentException(nameof(polyline));
+ }
+
+ int index = 0;
+ int latitude = 0;
+ int longitude = 0;
+
+ while (index < polyline.Length)
+ {
+ if (!TryCalculateNext(ref polyline, ref index, ref latitude))
+ {
+ throw new InvalidOperationException();
+ }
+
+ if (!TryCalculateNext(ref polyline, ref index, ref longitude))
+ {
+ throw new InvalidOperationException();
+ }
+
+ var coordinate = (GetDoubleRepresentation(latitude), GetDoubleRepresentation(longitude));
+
+ if (!CoordinateValidator.IsValid(coordinate))
+ {
+ throw new InvalidOperationException();
+ }
+
+ yield return (latitude, longitude);
+ }
+ }
+
+ private static bool TryCalculateNext(ref char[] polyline, ref int index, ref int value)
+ {
+ int chunk;
+ int sum = 0;
+ int shifter = 0;
+
+ do
+ {
+ chunk = polyline[index++] - Constants.ASCII.QuestionMark;
+ sum |= (chunk & Constants.ASCII.UnitSeparator) << shifter;
+ shifter += Constants.ShiftLength;
+ } while (chunk >= Constants.ASCII.Space && index < polyline.Length);
+
+ if (index >= polyline.Length && chunk >= Constants.ASCII.Space)
+ return false;
+
+ value += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1;
+
+ return true;
+ }
+
+ private static double GetDoubleRepresentation(int value)
+ {
+ return Convert.ToDouble(value) / Constants.Precision;
+ }
+
+ public static class CoordinateValidator
+ {
+ public static bool IsValid((double Latitude, double Longitude) coordinate)
+ {
+ return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude);
+ }
+
+ public static bool IsValidLatitude(double latitude)
+ {
+ return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude;
+ }
+
+ public static bool IsValidLongitude(double longitude)
+ {
+ return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude;
+ }
+ }
+ }
+ }
+}
diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj
new file mode 100644
index 00000000..d799ba3b
--- /dev/null
+++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs
new file mode 100644
index 00000000..dff6e192
--- /dev/null
+++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs
@@ -0,0 +1,222 @@
+namespace DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks
+{
+ using BenchmarkDotNet.Attributes;
+ using BenchmarkDotNet.Engines;
+ using Microsoft.Extensions.ObjectPool;
+ using System.Collections.Generic;
+ using System.Text;
+
+ [MemoryDiagnoser]
+ public class EncodePerformanceBenchmark
+ {
+ private Consumer _consumer = new Consumer();
+
+ public IEnumerable<(int, IEnumerable<(double, double)>)> Coordinates()
+ {
+ yield return (1, new[] { (49.47383, 59.06250), (-58.37407, 25.31250), (52.99363, -120.93750), (-44.49024, -174.37500) });
+ yield return (2, new[] { (42.88895, -100.30630), (44.91513, 19.22495), (20.40244, 7.97495), (-15.52130, -63.74380), (-78.95116, -72.18130), (38.63072, 88.13120), (60.81071, 151.41245), (-58.20769, -173.43130), (59.40939, 83.91245), (-58.20769, 61.41245), (-20.86278, -119.99380), (34.10374, -150.93130), (-71.15367, 31.88120), (-72.04138, -153.74380), (-49.99635, -107.33755), (76.12614, 135.94370), (70.05664, 41.72495), (63.43879, -77.80630), (13.68456, -90.46255), (-75.90519, -7.49380), (74.71112, -127.02505), (-66.61109, 17.81870), (-49.08384, 37.50620) });
+ yield return (3, new[] { (60.81071, -121.40005), (70.05664, -38.43130), (37.52379, -84.83755), (41.85003, 26.25620), (68.04709, 110.63120), (61.48922, 50.16245), (-4.46018, -58.11880), (-32.16061, -3.27505), (-50.89185, -55.30630), (-28.52070, 90.94370), (35.26009, 93.75620), (54.83622, 128.91245), (1.16022, 37.50620), (-44.26398, -131.24380), (-33.34325, 154.22495), (-59.65879, 90.94370), (-62.38215, 0.94370), (72.32117, 40.31870), (64.66910, 2.34995), (-61.04971, -84.83755), (77.10238, -91.86880), (-72.88859, -129.83755), (-69.24987, -24.36880), (77.41254, 119.06870), (-70.69409, 83.91245), (78.85650, 75.47495), (26.83989, 140.16245), (-24.75069, -108.74380), (30.53968, -145.30630), (79.12503, 145.78745), (-34.51006, 133.13120), (-73.29753, -60.93130), (-74.08712, 23.44370), (-76.57404, 100.78745), (-76.57404, 100.78745), (39.72082, 103.59995), (70.99412, 148.59995), (82.27591, 138.75620), (78.29964, -3.27505), (78.29964, -3.27505), (-8.65039, 47.34995) });
+ }
+
+ [Benchmark(Baseline = true)]
+ [ArgumentsSource(nameof(Coordinates))]
+ public void Encode_V1((int, IEnumerable<(double, double)>) arg) => V1.Encode(arg.Item2).Consume(_consumer);
+
+ [Benchmark]
+ [ArgumentsSource(nameof(Coordinates))]
+ public void Encode_V1_Parallel((int, IEnumerable<(double, double)>) arg) => Parallel.For(100, 200, (i) => V1.Encode(arg.Item2).Consume(_consumer));
+
+
+ [Benchmark]
+ [ArgumentsSource(nameof(Coordinates))]
+ public void Encode_V2((int, IEnumerable<(double, double)>) arg) => V2.Encode(arg.Item2).Consume(_consumer);
+
+
+ [Benchmark]
+ [ArgumentsSource(nameof(Coordinates))]
+ public void Encode_V2_Parallel((int, IEnumerable<(double, double)>) arg) => Parallel.For(100, 200, (i) => V2.Encode(arg.Item2).Consume(_consumer));
+
+ private class V1
+ {
+ public static string Encode(IEnumerable<(double Latitude, double Longitude)> coordinates)
+ {
+ if (coordinates is null || !coordinates.Any())
+ {
+ throw new ArgumentException(nameof(coordinates));
+ }
+
+ EnsureCoordinates(coordinates);
+
+ int lastLatitude = 0;
+ int lastLongitude = 0;
+ var sb = new StringBuilder();
+
+ foreach (var coordinate in coordinates)
+ {
+ int latitude = GetIntegerRepresentation(coordinate.Latitude);
+ int longitude = GetIntegerRepresentation(coordinate.Longitude);
+
+ sb.Append(GetEncodedCharacters(latitude - lastLatitude).ToArray());
+ sb.Append(GetEncodedCharacters(longitude - lastLongitude).ToArray());
+
+ lastLatitude = latitude;
+ lastLongitude = longitude;
+ }
+
+ return sb.ToString();
+ }
+
+ private static void EnsureCoordinates(IEnumerable<(double Latitude, double Longitude)> coordinates)
+ {
+ var invalidCoordinates = coordinates
+ .Where(c => !CoordinateValidator.IsValid(c));
+
+ if (invalidCoordinates.Any())
+ {
+ throw new AggregateException(
+ invalidCoordinates
+ .Select(c =>
+ new ArgumentOutOfRangeException()
+ )
+ );
+ }
+ }
+
+ private static IEnumerable GetEncodedCharacters(int value)
+ {
+ int shifted = value << 1;
+ if (value < 0)
+ shifted = ~shifted;
+
+ int rem = shifted;
+
+ while (rem >= Constants.ASCII.Space)
+ {
+ yield return (char)((Constants.ASCII.Space | rem & Constants.ASCII.UnitSeparator) + Constants.ASCII.QuestionMark);
+
+ rem >>= Constants.ShiftLength;
+ }
+
+ yield return (char)(rem + Constants.ASCII.QuestionMark);
+ }
+
+ private static int GetIntegerRepresentation(double value)
+ {
+ return (int)Math.Round(value * Constants.Precision);
+ }
+
+ public static class CoordinateValidator
+ {
+ public static bool IsValid((double Latitude, double Longitude) coordinate)
+ {
+ return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude);
+ }
+
+ public static bool IsValidLatitude(double latitude)
+ {
+ return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude;
+ }
+
+ public static bool IsValidLongitude(double longitude)
+ {
+ return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude;
+ }
+ }
+ }
+
+ private class V2
+ {
+ private static readonly ObjectPool _pool = new DefaultObjectPoolProvider().CreateStringBuilderPool(5, int.MaxValue);
+
+ public static string Encode(IEnumerable<(double Latitude, double Longitude)> coordinates)
+ {
+ if (coordinates is null || !coordinates.Any())
+ {
+ throw new ArgumentException(nameof(coordinates));
+ }
+
+ EnsureCoordinates(coordinates);
+
+ int lastLatitude = 0;
+ int lastLongitude = 0;
+
+ var sb = _pool.Get();
+
+ foreach (var coordinate in coordinates)
+ {
+ int latitude = GetIntegerRepresentation(coordinate.Latitude);
+ int longitude = GetIntegerRepresentation(coordinate.Longitude);
+
+ sb.Append(GetEncodedCharacters(latitude - lastLatitude).ToArray());
+ sb.Append(GetEncodedCharacters(longitude - lastLongitude).ToArray());
+
+ lastLatitude = latitude;
+ lastLongitude = longitude;
+ }
+
+ var result = sb.ToString();
+
+ _pool.Return(sb);
+
+ return result;
+ }
+
+ private static void EnsureCoordinates(IEnumerable<(double Latitude, double Longitude)> coordinates)
+ {
+ var invalidCoordinates = coordinates
+ .Where(c => !CoordinateValidator.IsValid(c));
+
+ if (invalidCoordinates.Any())
+ {
+ throw new AggregateException(
+ invalidCoordinates
+ .Select(c =>
+ new ArgumentOutOfRangeException()
+ )
+ );
+ }
+ }
+
+ private static IEnumerable GetEncodedCharacters(int value)
+ {
+ int shifted = value << 1;
+ if (value < 0)
+ shifted = ~shifted;
+
+ int rem = shifted;
+
+ while (rem >= Constants.ASCII.Space)
+ {
+ yield return (char)((Constants.ASCII.Space | rem & Constants.ASCII.UnitSeparator) + Constants.ASCII.QuestionMark);
+
+ rem >>= Constants.ShiftLength;
+ }
+
+ yield return (char)(rem + Constants.ASCII.QuestionMark);
+ }
+
+ private static int GetIntegerRepresentation(double value)
+ {
+ return (int)Math.Round(value * Constants.Precision);
+ }
+
+ public static class CoordinateValidator
+ {
+ public static bool IsValid((double Latitude, double Longitude) coordinate)
+ {
+ return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude);
+ }
+
+ public static bool IsValidLatitude(double latitude)
+ {
+ return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude;
+ }
+
+ public static bool IsValidLongitude(double longitude)
+ {
+ return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude;
+ }
+ }
+ }
+ }
+}
diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Program.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Program.cs
new file mode 100644
index 00000000..1a591c1c
--- /dev/null
+++ b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Program.cs
@@ -0,0 +1,15 @@
+namespace DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks
+{
+ using BenchmarkDotNet.Running;
+
+ internal class Program
+ {
+ static void Main(string[] args)
+ {
+ BenchmarkRunner
+ .Run();
+ BenchmarkRunner
+ .Run();
+ }
+ }
+}