Skip to content

Commit a32a8f4

Browse files
author
Petr Sramek
committed
refact
1 parent b2044d4 commit a32a8f4

File tree

12 files changed

+196
-56
lines changed

12 files changed

+196
-56
lines changed

benchmarks/PolylineAlgorithm.Benchmarks/Internal/ValueProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
public static class ValueProvider
66
{
7-
private static ConcurrentDictionary<int, CoordinatePair> _cache = new();
8-
private static PolylineEncoder _encoder = new();
7+
private static readonly ConcurrentDictionary<int, CoordinatePair> _cache = new();
8+
private static readonly PolylineEncoder _encoder = new();
99

1010
public static IEnumerable<Coordinate> GetCoordinates(int count) {
1111
var entry = GetCaheEntry(count);

benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace PolylineAlgorithm.Benchmarks;
1818
public class PolylineDecoderBenchmark {
1919
private readonly Consumer _consumer = new();
2020

21-
[Params(10, 100, 1_000, 10_000, 100_000)]
21+
[Params(1, 10, 100, 1_000, 10_000, 100_000, 1_000_000)]
2222
public int N;
2323

2424
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.

benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,17 @@
66
namespace PolylineAlgorithm.Benchmarks;
77

88
using BenchmarkDotNet.Attributes;
9-
using Microsoft.Extensions.ObjectPool;
109
using PolylineAlgorithm;
11-
using PolylineAlgorithm.Abstraction;
1210
using PolylineAlgorithm.Benchmarks.Internal;
13-
using System;
1411
using System.Collections.Generic;
15-
using System.Text;
1612
using System.Threading.Tasks;
1713

1814
/// <summary>
1915
/// Benchmarks for the <see cref="PolylineEncoder"/> class.
2016
/// </summary>
2117
[RankColumn]
2218
public class PolylineEncoderBenchmark {
23-
private static string _dir = "C:\\temp_benchmark";
24-
private static StringBuilderPooledObjectPolicy _policy = new();
25-
26-
[Params(10, 100, 1_000, 10_000, 100_000)]
19+
[Params(1, 10, 100, 1_000, 10_000, 100_000, 1_000_000)]
2720
public int N;
2821

2922
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
@@ -55,7 +48,6 @@ public class PolylineEncoderBenchmark {
5548
/// </summary>
5649
[GlobalSetup]
5750
public void SetupData() {
58-
Directory.CreateDirectory(_dir);
5951
Enumeration = ValueProvider.GetCoordinates(N);
6052
List = [.. Enumeration];
6153
AsyncEnumeration = GetAsyncEnumeration(Enumeration!);
@@ -94,10 +86,16 @@ public Polyline PolylineEncoder_Encode_Enumerator() {
9486
/// </summary>
9587
/// <returns>The encoded polyline.</returns>
9688
[Benchmark]
97-
public async Task PolylineEncoder_EncodeAsync_String() {
89+
public async Task<Polyline> PolylineEncoder_EncodeAsync_String() {
9890
var result = AsyncEncoder
9991
.EncodeAsync(AsyncEnumeration!);
10092

101-
await foreach (var _ in result) { }
93+
var polyline = new Polyline();
94+
95+
await foreach (var item in result.ConfigureAwait(false)) {
96+
polyline.Append(item);
97+
}
98+
99+
return polyline;
102100
}
103101
}

src/PolylineAlgorithm/Abstraction/IAsyncPolylineEncoder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public interface IAsyncPolylineEncoder {
1515
/// Converts a set of coordinates to an encoded polyline.
1616
/// </summary>
1717
/// <param name="coordinates">A set of coordinates to encode.</param>
18+
/// <param name="cancellation">A cancellation token.</param>
1819
/// <returns>An encoded polyline representing the set of coordinates.</returns>
19-
IAsyncEnumerable<Polyline> EncodeAsync(IAsyncEnumerable<Coordinate> coordinates);
20+
IAsyncEnumerable<Polyline> EncodeAsync(IAsyncEnumerable<Coordinate> coordinates, CancellationToken? cancellation);
2021
}

src/PolylineAlgorithm/AsyncPolylineEncoder.cs

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,39 +13,24 @@ namespace PolylineAlgorithm;
1313
/// Performs polyline algorithm decoding
1414
/// </summary>
1515
public class AsyncPolylineEncoder : IAsyncPolylineEncoder {
16-
private uint _batchSize = 10000;
17-
1816
/// <inheritdoc />
1917
/// <exception cref="ArgumentException">Thrown when <paramref name="polyline"/> argument is null -or- empty.</exception>
2018
/// <exception cref="InvalidOperationException">Thrown when <paramref name="polyline"/> is not in correct format.</exception>
21-
public async IAsyncEnumerable<Polyline> EncodeAsync(IAsyncEnumerable<Coordinate> coordinates) {
19+
public async IAsyncEnumerable<Polyline> EncodeAsync(IAsyncEnumerable<Coordinate> coordinates, CancellationToken? cancellation = null) {
2220
if (coordinates is null) {
2321
throw new ArgumentNullException(nameof(coordinates));
2422
}
2523

26-
ulong index = 0;
2724
CoordinateDifference diff = new();
28-
Polyline polyline = new();
2925

3026
await foreach (var coordinate in coordinates.ConfigureAwait(false)) {
3127
InvalidCoordinateException.ThrowIfNotValid(coordinate);
3228

3329
diff.DiffNext(coordinate);
3430

3531
var result = EncodingAlgorithm.EncodeNext(diff.Latitude, diff.Longitude);
36-
37-
polyline = polyline
38-
.Append(Polyline.FromMemory(result));
39-
40-
index++;
41-
42-
if (index == _batchSize) {
43-
var temp = polyline;
44-
45-
polyline = new();
4632

47-
yield return temp;
48-
}
33+
yield return new Polyline(result);
4934
}
5035
}
5136
}
Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
using System.Diagnostics;
2+
using System.Runtime.CompilerServices;
23

3-
namespace PolylineAlgorithm.Internal
4-
{
4+
namespace PolylineAlgorithm.Internal {
55
[DebuggerDisplay($"{{{nameof(ToString)}(),nq}}")]
6-
internal struct CoordinateDifference
7-
{
6+
internal struct CoordinateDifference {
87
public CoordinateDifference() {
98
Coordinate = default;
109
Latitude = default;
@@ -18,17 +17,35 @@ public CoordinateDifference() {
1817
public override string ToString()
1918
=> $"Latitude: {Latitude}, Longitude: {Longitude}";
2019

20+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2121
public void DiffNext(Coordinate next) {
22-
Coordinate.Imprecise(out int currentLatitude, out int currentLongitude);
22+
var current = Exchange(next);
23+
24+
current.Imprecise(out int currentLatitude, out int currentLongitude);
2325
next.Imprecise(out int nextLatitude, out int nextLongitude);
2426

25-
Coordinate = next;
2627
Latitude = Difference(currentLatitude, nextLatitude);
2728
Longitude = Difference(currentLongitude, nextLongitude);
29+
}
30+
31+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
32+
private static int Difference(int first, int second) => (first, second) switch {
33+
(0, 0) => 0,
34+
(0, _) => second,
35+
(_, 0) => -first,
36+
( < 0, < 0) => -(Math.Abs(second) - Math.Abs(first)),
37+
( < 0, > 0) => second + Math.Abs(first),
38+
( > 0, < 0) => -(Math.Abs(second) + first),
39+
( > 0, > 0) => second - first,
40+
};
41+
42+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
43+
private Coordinate Exchange(Coordinate value) {
44+
var current = Coordinate;
45+
46+
Coordinate = value;
2847

29-
static int Difference(int first, int second) {
30-
return Math.Max(first, second) + Math.Min(first, second);
31-
}
48+
return current;
3249
}
3350
}
3451
}

src/PolylineAlgorithm/Internal/EncodingAlgorithm.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ internal static ReadOnlyMemory<char> EncodeNext(int latitudeDiff, int longitudeD
1515
index = EncodeDifference(ref latitudeDiff, ref index, ref buffer);
1616
index = EncodeDifference(ref longitudeDiff, ref index, ref buffer);
1717

18-
return buffer.ToArray();
18+
return buffer[..index].ToArray();
1919
}
2020

2121
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -38,7 +38,7 @@ private static ref int EncodeDifference(ref int difference, ref int index, ref S
3838

3939
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
4040
private static int GetRequiredCharCount(int difference) => difference switch {
41-
// DO NOT CHANGE THE ORDER. We are skipping inside ranges as those are covered by previous statements.
41+
// DO NOT CHANGE THE ORDER. We are skipping inside exclusive ranges as those are covered by previous statements.
4242
>= -16 and <= +15 => 1,
4343
>= -512 and <= +511 => 2,
4444
>= -16384 and <= +16383 => 3,

src/PolylineAlgorithm/Internal/PolylineSequence.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,12 @@ public PolylineSegment(ReadOnlyMemory<char> memory, long runningIndex = 0) {
99
}
1010

1111
public PolylineSegment Append(ReadOnlyMemory<char> memory) {
12-
var segment = new PolylineSegment(memory, RunningIndex + memory.Length);
13-
14-
Next = segment;
15-
16-
return segment;
12+
return Append(new PolylineSegment(memory));
1713
}
1814

1915
public PolylineSegment Append(PolylineSegment next) {
16+
next.RunningIndex = RunningIndex + Memory.Length;
17+
2018
Next = next;
2119

2220
return next;

src/PolylineAlgorithm/Polyline.cs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ namespace PolylineAlgorithm;
1212
using System.Diagnostics;
1313
using System.Diagnostics.CodeAnalysis;
1414
using System.Runtime.InteropServices;
15+
using System.Text;
16+
using System.Text.RegularExpressions;
1517

1618
/// <summary>
1719
/// Represents a readonly encoded polyline string.
@@ -97,7 +99,7 @@ public Polyline(ReadOnlyMemory<char> value) {
9799
/// Returns a string representation of the value of this instance.
98100
/// </summary>
99101
/// <returns>The string value of this <see cref="Polyline"/> object.</returns>
100-
public override string ToString() => _value.ToString();
102+
public override string ToString() => Value.ToString();
101103

102104
public Polyline Append(Polyline other) {
103105
if (other.IsEmpty) {
@@ -151,7 +153,42 @@ private PolylineSegment GetSegments(out PolylineSegment initial) {
151153
#region IEquatable<Polyline> implementation
152154

153155
/// <inheritdoc />
154-
public bool Equals(Polyline other) => Value.Equals(other.Value);
156+
public bool Equals(Polyline other) {
157+
if(IsEmpty && other.IsEmpty) {
158+
return true;
159+
}
160+
161+
if(Length != other.Length) {
162+
return false;
163+
}
164+
165+
long start = 0;
166+
bool result = true;
167+
int chunkSize = 256;
168+
long max = Math.DivRem(Length, chunkSize, out long remainder) + (remainder > 0 ? 1 : 0);
169+
Span<char> @this = stackalloc char[chunkSize];
170+
Span<char> that = stackalloc char[chunkSize];
171+
172+
for (int i = 0; i < max; i++) {
173+
start = i * chunkSize;
174+
175+
if(max - i == 1) {
176+
@this.Clear();
177+
that.Clear();
178+
chunkSize = (int)remainder;
179+
}
180+
181+
Value.Slice(start, chunkSize).CopyTo(@this);
182+
other.Value.Slice(start, chunkSize).CopyTo(that);
183+
184+
if(!@this.SequenceEqual(that)) {
185+
result = false;
186+
break;
187+
}
188+
}
189+
190+
return result;
191+
}
155192

156193
#endregion
157194

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//
2+
// Copyright © Pete Sramek. All rights reserved.
3+
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
4+
//
5+
6+
namespace PolylineAlgorithm.Tests;
7+
8+
using PolylineAlgorithm;
9+
using PolylineAlgorithm.Tests.Data;
10+
11+
/// <summary>
12+
/// Defines tests for the <see cref="PolylineDecoder"/> type.
13+
/// </summary>
14+
[TestClass]
15+
public class AsyncPolylineEncoderTest {
16+
/// <summary>
17+
/// The instance of the <see cref="PolylineDecoder"/> used for testing.
18+
/// </summary>
19+
public AsyncPolylineEncoder Encoder = new();
20+
21+
22+
23+
private async IAsyncEnumerable<Coordinate> GetAsyncEnumeration(IEnumerable<Coordinate> enumerable) {
24+
foreach (var item in enumerable) {
25+
yield return await new ValueTask<Coordinate>(item);
26+
}
27+
}
28+
29+
/// <summary>
30+
/// Tests the <see cref="PolylineDecoder.Decode(ref readonly Polyline)"/> method with an empty input, expecting an <see cref="ArgumentException"/>.
31+
/// </summary>
32+
[TestMethod]
33+
public async Task Decode_Null_Input_ThrowsException() {
34+
// Arrange
35+
IAsyncEnumerable<Coordinate> @null = null!;
36+
37+
// Act
38+
async Task<Polyline> Execute(IAsyncEnumerable<Coordinate> value) {
39+
var result = new Polyline();
40+
41+
await foreach (var polyline in Encoder.EncodeAsync(value)) {
42+
result = result
43+
.Append(polyline);
44+
}
45+
46+
return result;
47+
};
48+
49+
// Assert
50+
await Assert.ThrowsExactlyAsync<ArgumentNullException>(async () => await Execute(@null));
51+
}
52+
53+
///// <summary>
54+
///// Tests the <see cref="PolylineDecoder.Decode(ref readonly Polyline)"/> method with an invalid input, expecting an <see cref="InvalidCoordinateException"/>.
55+
///// </summary>
56+
//[TestMethod]
57+
//public async Task Decode_Invalid_Input_ThrowsException() {
58+
// // Arrange
59+
// Polyline invalid = new(Values.Polyline.Invalid);
60+
61+
// // Act
62+
// async Task<IEnumerable<Coordinate>> Execute(Polyline value) {
63+
// var result = new List<Coordinate>();
64+
65+
// await foreach (var coordinate in Decoder.DecodeAsync(value)) {
66+
// result.Add(coordinate);
67+
// }
68+
69+
// return result;
70+
// }
71+
// ;
72+
73+
// // Assert
74+
// await Assert.ThrowsExactlyAsync<InvalidCoordinateException>(async () => await Execute(invalid));
75+
//}
76+
77+
/// <summary>
78+
/// Tests the <see cref="PolylineDecoder.Decode(ref readonly Polyline)"/> method with a valid input.
79+
/// </summary>
80+
/// <remarks>Expected result to equal <see cref="Values.Coordinates.Valid"/>.</remarks>
81+
[TestMethod]
82+
public async Task Decode_Valid_Input_Ok() {
83+
// Arrange
84+
IAsyncEnumerable<Coordinate> valid = GetAsyncEnumeration(Values.Coordinates.Valid);
85+
86+
// Act
87+
var collection = new List<Polyline>();
88+
89+
var result = new Polyline();
90+
91+
await foreach (var polyline in Encoder.EncodeAsync(valid)) {
92+
collection
93+
.Add(polyline);
94+
result = result
95+
.Append(polyline);
96+
}
97+
98+
// Assert
99+
Assert.AreEqual(Values.Coordinates.Valid.Count(), collection.Count);
100+
Assert.AreEqual(Values.Polyline.Valid, result.ToString());
101+
Assert.AreEqual(Polyline.FromString(Values.Polyline.Valid), result);
102+
}
103+
}

0 commit comments

Comments
 (0)