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

Commit

Permalink
Modifiy multiplication table to use async LINQ
Browse files Browse the repository at this point in the history
Also makes it an async enumerable instead of a static class so it's nicer to invoke.
Add a one-line multiplication table algorithm (unused).
Prime generator is no longer a synchronous enumerable now that we have async LINQ.
  • Loading branch information
langsamu committed Jun 12, 2020
1 parent 10492d7 commit 276ca4a
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 108 deletions.
4 changes: 4 additions & 0 deletions ClassLibrary1/ClassLibrary1.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Linq.Async" Version="4.1.1" />
</ItemGroup>

</Project>
66 changes: 56 additions & 10 deletions ClassLibrary1/MultiplicationTable.cs
Original file line number Diff line number Diff line change
@@ -1,37 +1,83 @@
namespace ClassLibrary1
{
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

public static class MultiplicationTable
public sealed class MultiplicationTable : IAsyncEnumerable<IAsyncEnumerable<int?>>
{
public static async IAsyncEnumerable<IAsyncEnumerable<int?>> GenerateAsync(int count, [EnumeratorCancellation]CancellationToken cancellationToken = default, PrimeGeneratorOptions options = PrimeGeneratorOptions.None)
private readonly int count;
private readonly PrimeGeneratorOptions options;

public MultiplicationTable(int count)
: this(count, PrimeGeneratorOptions.None)
{
}

public MultiplicationTable(int count, PrimeGeneratorOptions options)
{
this.count = count;
this.options = options;
}

public async IAsyncEnumerator<IAsyncEnumerable<int?>> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
// header row
yield return GenerateRowAsync(count, options, cancellationToken);
yield return this.GenerateRowAsync(cancellationToken);

var primes = new PrimeGenerator(options).GetAsyncEnumerator(cancellationToken);
var primes = new PrimeGenerator(this.options).Take(this.count);

// rest of rows
for (var i = 0; i < count && await primes.MoveNextAsync(); i++)
await foreach (var prime in primes.WithCancellation(cancellationToken))
{
yield return GenerateRowAsync(count, options, cancellationToken, primes.Current);
yield return this.GenerateRowAsync(cancellationToken, prime);
}
}

private static async IAsyncEnumerable<int?> GenerateRowAsync(int count, PrimeGeneratorOptions options, [EnumeratorCancellation]CancellationToken cancellationToken = default, int? header = null)
private async IAsyncEnumerable<int?> GenerateRowAsync([EnumeratorCancellation] CancellationToken cancellationToken, int? header = null)
{
// header column cell
yield return header;

var primes = new PrimeGenerator(options).GetAsyncEnumerator(cancellationToken);
var primes = new PrimeGenerator(this.options).Take(this.count);

// rest of cells
for (var i = 0; i < count && await primes.MoveNextAsync(); i++)
await foreach (var prime in primes.WithCancellation(cancellationToken))
{
yield return (header ?? 1) * primes.Current;
yield return (header ?? 1) * prime;
}
}

[SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "It's really just for fun")]
private static ConfiguredCancelableAsyncEnumerable<IAsyncEnumerable<int?>> OneLinerJustForFun(int count, CancellationToken cancellationToken = default, PrimeGeneratorOptions options = PrimeGeneratorOptions.None) =>
/* Elements of the produced table as they
* relate to lines in the algorithm below:
*
* @4 @5
* ⭨ ┌─────┴─────┐
* ┌ ␀ 2 3 5 7
* │ 2 4 6 10 14
* @3 ┤ 3 6 9 15 21
* │ 5 10 15 25 35
* └ 7 14 21 35 49
* ↑
* @11
*/

/* 1: */ new PrimeGenerator(options)
/* 2: */ .Take(count) // number of rows
/* 3: */ .Select(prime => (int?)prime) // make nullable for topmost cell
/* 4: */ .Prepend(null) // column header (topmost) cell
/* 5: */ .Select(rowHeader => // rows
/* 6: */ new PrimeGenerator(options)
/* 7: */ .Take(count) // number of columns
/* 8: */ .Select(prime => (int?)prime) // make nullable for leftmost cell
/* 9: */ .Select(columnHeader => // cells
/* 10: */ (rowHeader ?? 1) * columnHeader) // coalesce multiplicative identity for headerless top row
/* 11: */ .Prepend(rowHeader)) // row header (leftmost) cell
/* 12: */ .WithCancellation(cancellationToken);
}
}
32 changes: 0 additions & 32 deletions ClassLibrary1/PrimeGenerator.Enumerator.cs

This file was deleted.

8 changes: 1 addition & 7 deletions ClassLibrary1/PrimeGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
namespace ClassLibrary1
{
using System.Collections;
using System.Collections.Generic;
using System.Threading;

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

Expand All @@ -13,10 +12,5 @@ public PrimeGenerator(PrimeGeneratorOptions options = PrimeGeneratorOptions.None

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

public IEnumerator<int> GetEnumerator() =>
new Enumerator(new AsyncEnumerator());

IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
}
4 changes: 2 additions & 2 deletions ConsoleApp1/PrimeMultiplicationCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ private async Task ExecuteAsync(int size, int? timeout, bool throwOnCancel, ICon
PrimeGeneratorOptions.ThrowOnCancel :
PrimeGeneratorOptions.None;

var table = MultiplicationTable.GenerateAsync(size, cancellationToken, options);
var table = new MultiplicationTable(size, options);

await foreach (var row in table)
await foreach (var row in table.WithCancellation(cancellationToken))
{
await foreach (var cell in row)
{
Expand Down
53 changes: 6 additions & 47 deletions UnitTestProject1/UnitTest1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ namespace UnitTestProject1
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ClassLibrary1;
Expand All @@ -12,69 +10,30 @@ namespace UnitTestProject1
[TestClass]
public class UnitTest1
{
[TestMethod]
public async Task ProofOfConcept()
{
const int n = 10;
var primes = new PrimeGenerator().Take(n).ToArray();

var largestPrime = primes.Last();
var largestMultiple = largestPrime * largestPrime;
var cellWidth = largestMultiple.ToString(CultureInfo.InvariantCulture).Length + 1;
var format = $"{{0, {cellWidth}}}"; // Padding composite formatting string

// Header row
WriteRow();

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

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

foreach (var prime in primes)
{
WriteCell(prime, rowHeader ?? 1);
}

Console.WriteLine(); // LF
}

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

[TestMethod]
public async Task ProofOfConceptAsync()
{
using var cts = new CancellationTokenSource(100);
var table = MultiplicationTable.GenerateAsync(1000, cts.Token, PrimeGeneratorOptions.ThrowOnCancel);
var table = new MultiplicationTable(1000, PrimeGeneratorOptions.ThrowOnCancel);

await Assert.ThrowsExceptionAsync<OperationCanceledException>(async () =>
{
await Write(table);
await Write(table, cts.Token);
});
}

[TestMethod]
public async Task ProofOfConceptAsyncDontThrow()
{
using var cts = new CancellationTokenSource(100);
var table = MultiplicationTable.GenerateAsync(1000, cts.Token);
var table = new MultiplicationTable(1000);

await Write(table);
await Write(table, cts.Token);
}

private static async Task Write(IAsyncEnumerable<IAsyncEnumerable<int?>> table)
private static async Task Write(MultiplicationTable table, CancellationToken cancellationToken)
{
await foreach (var row in table)
await foreach (var row in table.WithCancellation(cancellationToken))
{
await foreach (var cell in row)
{
Expand Down
9 changes: 2 additions & 7 deletions WebApplication1/Controllers/Default.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
namespace WebApplication1.Controllers
{
using System;
using System.Linq;
using System.Threading;
using ClassLibrary1;
using Microsoft.AspNetCore.Diagnostics;
Expand All @@ -13,10 +12,6 @@ public class Default : Controller
{
private const int timeout = 1000;

[HttpGet("{thisMany}")]
public IActionResult Index(int thisMany) =>
this.View(new PrimeGenerator().Take(thisMany));

[HttpGet("async/{thisMany}")]
public IActionResult Cancellable(int thisMany, CancellationToken cancellationToken) =>
this.CancellableInternal(PrimeGeneratorOptions.ThrowOnCancel, thisMany, cancellationToken);
Expand All @@ -34,9 +29,9 @@ private IActionResult CancellableInternal(PrimeGeneratorOptions options, int thi

this.Response.RegisterForDispose(linkedCancellation);

var table = MultiplicationTable.GenerateAsync(thisMany, linkedCancellation.Token, options);
var table = new MultiplicationTable(thisMany, options);

return this.View("Cancellable", table);
return this.View("Cancellable", (table, linkedCancellation.Token));
}

[HttpGet("error")]
Expand Down
6 changes: 3 additions & 3 deletions WebApplication1/Views/Default/Cancellable.cshtml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@model IAsyncEnumerable<IAsyncEnumerable<int?>>
@model (IAsyncEnumerable<IAsyncEnumerable<int?>> Table, System.Threading.CancellationToken Timeout)

<!DOCTYPE html>
<html>
Expand Down Expand Up @@ -38,14 +38,14 @@
top: 0;
}
tr::not(:first-child) td {
tr:not(:first-child) td {
left: 0;
}
</style>
</head>
<body>
<table>
@await foreach (var row in this.Model)
@await foreach (var row in this.Model.Table.WithCancellation(this.Model.Timeout))
{
<tr>
@await foreach (var cell in row)
Expand Down

0 comments on commit 276ca4a

Please sign in to comment.