Skip to content
This repository has been archived by the owner on Jan 10, 2023. It is now read-only.

Commit

Permalink
Implement robust prime generator
Browse files Browse the repository at this point in the history
The generator can now be enumerated synchronously and asynchronously.
The async generator can now be cancelled abruptly (throw) or gracefully (just stop).
The generator is now in a separate library.
Tests and web app feature three scenarios:
1. Multiply first N primes synchronously, no fault handling.
2. Multiply first N primes asynchronously, fail if time limit exceeded.
3. Multiply as many of the first N primes as possible in time limit.
  • Loading branch information
langsamu committed Jun 11, 2020
1 parent ce35efb commit 849d1e0
Show file tree
Hide file tree
Showing 14 changed files with 254 additions and 150 deletions.
8 changes: 7 additions & 1 deletion AvastRecruitmentPrimeMultiplication.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ VisualStudioVersion = 16.0.30128.74
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTestProject1", "UnitTestProject1\UnitTestProject1.csproj", "{2C1DA99E-D388-484C-A81B-07C14664C950}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApplication1", "WebApplication1\WebApplication1.csproj", "{BBB20ED0-348D-4EBD-9FA7-63F5A2A28799}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApplication1", "WebApplication1\WebApplication1.csproj", "{BBB20ED0-348D-4EBD-9FA7-63F5A2A28799}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary1", "ClassLibrary1\ClassLibrary1.csproj", "{D44BA64C-FA3B-4A8C-AF05-3251988CB5DA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -21,6 +23,10 @@ Global
{BBB20ED0-348D-4EBD-9FA7-63F5A2A28799}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BBB20ED0-348D-4EBD-9FA7-63F5A2A28799}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BBB20ED0-348D-4EBD-9FA7-63F5A2A28799}.Release|Any CPU.Build.0 = Release|Any CPU
{D44BA64C-FA3B-4A8C-AF05-3251988CB5DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D44BA64C-FA3B-4A8C-AF05-3251988CB5DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D44BA64C-FA3B-4A8C-AF05-3251988CB5DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D44BA64C-FA3B-4A8C-AF05-3251988CB5DA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
7 changes: 7 additions & 0 deletions ClassLibrary1/ClassLibrary1.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

</Project>
88 changes: 88 additions & 0 deletions ClassLibrary1/PrimeGenerator.PrimeAsyncEnumerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
namespace ClassLibrary1
{
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public sealed partial class PrimeGenerator
{
private sealed class PrimeAsyncEnumerator : IAsyncEnumerator<int>
{
private const int notInitialised = 1;
private readonly PrimeGeneratorOptions options;
private readonly CancellationToken cancellationToken;
private int current = notInitialised;

internal PrimeAsyncEnumerator(PrimeGeneratorOptions options = PrimeGeneratorOptions.None, CancellationToken cancellationToken = default)
{
this.options = options;
this.cancellationToken = cancellationToken;
}

int IAsyncEnumerator<int>.Current
{
get
{
if (this.current == notInitialised)
{
throw new InvalidOperationException();
}

return this.current;
}
}

private bool IsPrime
{
get
{
for (int i = 2; i * i <= this.current; i++)
{
this.ThrowIfNeeded();

if (this.current % i == 0)
{
return false;
}
}

return true;
}
}

private bool ShouldStop =>
!this.ShouldThrowOnCancel && this.cancellationToken.IsCancellationRequested;

ValueTask IAsyncDisposable.DisposeAsync() =>
default;

ValueTask<bool> IAsyncEnumerator<int>.MoveNextAsync()
{
do
{
if (this.ShouldStop)
{
return new ValueTask<bool>(false);
}

this.current++;

} while (!this.IsPrime);

return new ValueTask<bool>(true);
}

private void ThrowIfNeeded()
{
if (this.ShouldThrowOnCancel)
{
this.cancellationToken.ThrowIfCancellationRequested();
}
}

private bool ShouldThrowOnCancel =>
this.options.HasFlag(PrimeGeneratorOptions.ThrowOnCancel);
}
}
}
32 changes: 32 additions & 0 deletions ClassLibrary1/PrimeGenerator.PrimeEnumerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace ClassLibrary1
{
using System;
using System.Collections;
using System.Collections.Generic;

public sealed partial class PrimeGenerator
{
private sealed class PrimeEnumerator : IEnumerator<int>
{
private readonly IAsyncEnumerator<int> primeAsyncEnumerator;

internal PrimeEnumerator(PrimeAsyncEnumerator primeAsyncEnumerator) =>
this.primeAsyncEnumerator = primeAsyncEnumerator;

public int Current =>
this.primeAsyncEnumerator.Current;

object IEnumerator.Current =>
this.Current;

void IDisposable.Dispose() =>
this.primeAsyncEnumerator.DisposeAsync().GetAwaiter().GetResult();

bool IEnumerator.MoveNext() =>
this.primeAsyncEnumerator.MoveNextAsync().GetAwaiter().GetResult();

void IEnumerator.Reset() =>
throw new InvalidOperationException();
}
}
}
22 changes: 22 additions & 0 deletions ClassLibrary1/PrimeGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace ClassLibrary1
{
using System.Collections;
using System.Collections.Generic;
using System.Threading;

public sealed partial class PrimeGenerator : IAsyncEnumerable<int>, IEnumerable<int>
{
private readonly PrimeGeneratorOptions options;

public PrimeGenerator(PrimeGeneratorOptions options = PrimeGeneratorOptions.None) =>
this.options = options;

public IAsyncEnumerator<int> GetAsyncEnumerator(CancellationToken cancellationToken = default) =>
new PrimeAsyncEnumerator(this.options, cancellationToken);

public IEnumerator<int> GetEnumerator() =>
new PrimeEnumerator(new PrimeAsyncEnumerator());

IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
}
11 changes: 11 additions & 0 deletions ClassLibrary1/PrimeGeneratorOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace ClassLibrary1
{
using System;

[Flags]
public enum PrimeGeneratorOptions
{
None = 0b_0,
ThrowOnCancel = 0b_1
}
}
64 changes: 0 additions & 64 deletions UnitTestProject1/Extensions.cs

This file was deleted.

48 changes: 42 additions & 6 deletions UnitTestProject1/UnitTest1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ namespace UnitTestProject1
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ClassLibrary1;
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
public class UnitTest1
{
[TestMethod]
public void ProofOfConcept()
public async Task ProofOfConcept()
{
const int n = 10;
var primes = n.Primes().ToArray();
var primes = new PrimeGenerator().Take(n).ToArray();

var largestPrime = primes.Last();
var largestMultiple = largestPrime * largestPrime;
Expand Down Expand Up @@ -54,15 +55,15 @@ public async Task ProofOfConceptAsync()
{
using var cts = new CancellationTokenSource(100);

const int n = 2000;
var generator = new PrimeGenerator(PrimeGeneratorOptions.ThrowOnCancel).WithCancellation(cts.Token);

await Assert.ThrowsExceptionAsync<TaskCanceledException>(async () =>
await Assert.ThrowsExceptionAsync<OperationCanceledException>(async () =>
{
// Header row
await WriteRow();
// Body
await foreach (var prime in n.PrimesAsync(cts.Token))
await foreach (var prime in generator)
{
await WriteRow(prime);
}
Expand All @@ -73,7 +74,42 @@ async Task WriteRow(int? rowHeader = null)
// Header column
WriteCell(rowHeader);

await foreach (var prime in n.PrimesAsync(cts.Token))
await foreach (var prime in generator)
{
WriteCell(prime, rowHeader ?? 1);
}

Console.WriteLine(); // LF
}

void WriteCell(int? prime1 = null, int? prime2 = 1)
{
Console.Write("{0, 10}", prime1 * prime2);
}
}

[TestMethod]
public async Task ProofOfConceptAsyncDontThrow()
{
using var cts = new CancellationTokenSource(100);

var generator = new PrimeGenerator().WithCancellation(cts.Token);

// Header row
await WriteRow();

// Body
await foreach (var prime in generator)
{
await WriteRow(prime);
}

async Task WriteRow(int? rowHeader = null)
{
// Header column
WriteCell(rowHeader);

await foreach (var prime in generator)
{
WriteCell(prime, rowHeader ?? 1);
}
Expand Down
4 changes: 4 additions & 0 deletions UnitTestProject1/UnitTestProject1.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj" />
</ItemGroup>

</Project>
Loading

0 comments on commit 849d1e0

Please sign in to comment.