Skip to content

Commit

Permalink
add optimized implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
e-silvestri committed Jan 9, 2022
1 parent 2250a14 commit 675015a
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,5 @@ _Pvt_Extensions
output.txt
multipleSolutions.txt
costs.txt

*.Artifacts
36 changes: 36 additions & 0 deletions PtVzzlexMasCake.Tests/BinaryPuzzleSolverTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Numerics;

namespace PtVzzlexMasCake.Tests
{
[TestClass]
public class BinaryPuzzleSolverTests
{
[TestMethod]
[DataRow(10)]
[DataRow(100)]
[DataRow(1000)]
[DataRow(10000)]
public void SolveBigNumber(int digits)
{
var r = new Random(0);
var s = new char[digits];
for (int i = 0; i < digits; i++)
{
s[i] = r.Next(10).ToString().First();
}
var value = BigInteger.Parse(new string(s));

var expectedSolution = PuzzleSolver.Solve(value);
var solution = BinaryPuzzleSolver.Solve(value);
CollectionAssert.AreEqual(expectedSolution, solution);

var initialValue = value;
foreach (var operation in solution)
{
initialValue = Operator.Execute(operation, initialValue);
}
Assert.AreEqual(1, initialValue);
}
}
}
1 change: 1 addition & 0 deletions PtVzzlexMasCake.Tests/PuzzleSolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public void PuzzleSolverTest()
[DataRow(100)]
[DataRow(1000)]
[DataRow(10000)]
[DataRow(100000)]
public void SolveBigNumber(int digits)
{
var r = new Random(0);
Expand Down
36 changes: 36 additions & 0 deletions PtVzzlexMasCake/Benchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using BenchmarkDotNet.Attributes;
using System.Numerics;

namespace PtVzzlexMasCake
{
public class Benchmark
{
[Params(1 << 3, 1 << 4, 1 << 5, 1 << 6, 1 << 7, 1 << 8, 1 << 9, 1 << 10)]
public int Digits;

private BigInteger Value;

[GlobalSetup]
public void GlobalSetup()
{
var r = new Random(0);
var s = new char[Digits];
for (int i = 0; i < Digits; i++)
{
s[i] = r.Next(10).ToString().First();
}
Value = BigInteger.Parse(new string(s));
}

[Benchmark(Baseline = true)]
public void PuzzleSolver()
{
_ = PtVzzlexMasCake.PuzzleSolver.Solve(Value);
}
[Benchmark]
public void BinaryPuzzleSolver()
{
_ = PtVzzlexMasCake.BinaryPuzzleSolver.Solve(Value);
}
}
}
65 changes: 65 additions & 0 deletions PtVzzlexMasCake/BinaryPuzzleSolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System.Numerics;
using System.Reflection;
using System.Reflection.Emit;

namespace PtVzzlexMasCake
{
internal static class BinaryPuzzleSolver
{
static Func<BigInteger, uint[]?> GetBits = CreateGetter<BigInteger, uint[]?>(typeof(BigInteger).GetField("_bits", BindingFlags.NonPublic | BindingFlags.Instance));
static Func<BigInteger, int> GetSign = CreateGetter<BigInteger, int>(typeof(BigInteger).GetField("_sign", BindingFlags.NonPublic | BindingFlags.Instance));

public static List<Operation> Solve(BigInteger value)
{
var solution = new List<Operation>();
while (!value.IsOne)
{
if (value.IsEven)
{
solution.Add(Operation.Div);
value = value >> 1;
continue;
}

if (value == 3)
{
solution.Add(Operation.Sub);
value = value - 1;
continue;
}
var bits = GetBits(value);

var firstBits = bits?[0] ?? (uint)GetSign(value);

if ((firstBits & 3) == 3)
{
solution.Add(Operation.Add);
value = value + 1;
}
else
{
solution.Add(Operation.Sub);
value = value - 1;
}
}
return solution;
}
static Func<S, T> CreateGetter<S, T>(FieldInfo field)
{
string methodName = field.ReflectedType.FullName + ".get_" + field.Name;
DynamicMethod getterrMethod = new DynamicMethod(methodName, typeof(T), new Type[1] { typeof(S) }, true);
ILGenerator gen = getterrMethod.GetILGenerator();
if (field.IsStatic)
{
gen.Emit(OpCodes.Ldsfld, field);
}
else
{
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Ldfld, field);
}
gen.Emit(OpCodes.Ret);
return (Func<S, T>)getterrMethod.CreateDelegate(typeof(Func<S, T>));
}
}
}
15 changes: 12 additions & 3 deletions PtVzzlexMasCake/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
using PtVzzlexMasCake;
using System.Numerics;
Runner.Run(args);
using BenchmarkDotNet.Running;

if (args.Contains("-benchmark"))
{
BenchmarkRunner.Run<Benchmark>();
}
else
{
Runner.Run(args);
}

public static class Runner
{
Expand All @@ -19,11 +28,11 @@ public static void Run(string[] args)
var s = new char[digits];
for (int i = 0; i < digits; i++)
{
s[i] = r.Next(10).ToString().First();
s[i] = r.Next(10).ToString().First();
}
value = BigInteger.Parse(new string(s));
}
var solution = PuzzleSolver.Solve(value);
var solution = BinaryPuzzleSolver.Solve(value);
using var sw = new StreamWriter(FileName);
sw.WriteLine(value.ToString());
foreach (var item in solution)
Expand Down
4 changes: 4 additions & 0 deletions PtVzzlexMasCake/PtVzzlexMasCake.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@
<_Parameter1>PtVzzlexMasCake.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
</ItemGroup>
</Project>
7 changes: 2 additions & 5 deletions PtVzzlexMasCake/PuzzleSolver.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Numerics;

namespace PtVzzlexMasCake
{
Expand All @@ -10,7 +7,7 @@ internal static class PuzzleSolver
public static List<Operation> Solve(BigInteger value)
{
var solution = new List<Operation>();
while (value != 1)
while (!value.IsOne)
{
if (value.IsEven)
{
Expand Down
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,48 @@ Div
```
## Solution graph (ALL efficient solutions displayed) form numbers up to 128
![Solution graph form numbers up to 128](graph.svg)

## Optimized implementation
The class [BinaryPuzzleSolver](PtVzzlexMasCake/BinaryPuzzleSolver.cs) contains an optimized implementation of the algorithm, up to five time faster than the standard one.

the following benchmark can be executed calling

dotnet run -- -benchmark

from the PtVzzlexMasCake directory.

``` ini

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.17134.2087 (1803/April2018Update/Redstone4)
Intel Core i7-4702MQ CPU 2.20GHz (Haswell), 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.101
[Host] : .NET 6.0.1 (6.0.121.56705), X64 RyuJIT
DefaultJob : .NET 6.0.1 (6.0.121.56705), X64 RyuJIT


```
| Method | Digits | Mean | Error | StdDev | Median | Ratio | RatioSD |
|------------------- |------- |-------------:|-----------:|-----------:|-------------:|------:|--------:|
| **PuzzleSolver** | **8** | **1.404 μs** | **0.0275 μs** | **0.0283 μs** | **1.395 μs** | **1.00** | **0.00** |
| BinaryPuzzleSolver | 8 | 1.559 μs | 0.0176 μs | 0.0156 μs | 1.552 μs | 1.11 | 0.03 |
| | | | | | | | |
| **PuzzleSolver** | **16** | **4.319 μs** | **0.0479 μs** | **0.0400 μs** | **4.306 μs** | **1.00** | **0.00** |
| BinaryPuzzleSolver | 16 | 3.357 μs | 0.1529 μs | 0.4508 μs | 3.617 μs | 0.71 | 0.10 |
| | | | | | | | |
| **PuzzleSolver** | **32** | **12.442 μs** | **0.1568 μs** | **0.1390 μs** | **12.412 μs** | **1.00** | **0.00** |
| BinaryPuzzleSolver | 32 | 8.435 μs | 0.1663 μs | 0.2103 μs | 8.328 μs | 0.68 | 0.02 |
| | | | | | | | |
| **PuzzleSolver** | **64** | **34.522 μs** | **0.4016 μs** | **0.3560 μs** | **34.504 μs** | **1.00** | **0.00** |
| BinaryPuzzleSolver | 64 | 17.982 μs | 0.2681 μs | 0.2377 μs | 17.919 μs | 0.52 | 0.01 |
| | | | | | | | |
| **PuzzleSolver** | **128** | **119.000 μs** | **2.3538 μs** | **2.4172 μs** | **118.719 μs** | **1.00** | **0.00** |
| BinaryPuzzleSolver | 128 | 47.461 μs | 1.3434 μs | 3.9610 μs | 45.496 μs | 0.43 | 0.02 |
| | | | | | | | |
| **PuzzleSolver** | **256** | **395.070 μs** | **7.7771 μs** | **7.9865 μs** | **394.613 μs** | **1.00** | **0.00** |
| BinaryPuzzleSolver | 256 | 116.538 μs | 0.5222 μs | 0.4361 μs | 116.418 μs | 0.29 | 0.01 |
| | | | | | | | |
| **PuzzleSolver** | **512** | **1,425.209 μs** | **8.6258 μs** | **7.2029 μs** | **1,421.555 μs** | **1.00** | **0.00** |
| BinaryPuzzleSolver | 512 | 335.446 μs | 2.9785 μs | 2.4872 μs | 334.431 μs | 0.24 | 0.00 |
| | | | | | | | |
| **PuzzleSolver** | **1024** | **5,520.100 μs** | **44.6069 μs** | **37.2488 μs** | **5,528.209 μs** | **1.00** | **0.00** |
| BinaryPuzzleSolver | 1024 | 1,023.062 μs | 4.5211 μs | 3.5298 μs | 1,021.721 μs | 0.19 | 0.00 |

0 comments on commit 675015a

Please sign in to comment.