Skip to content

Commit

Permalink
A couple of implementations tryed out. Benchmarking:
Browse files Browse the repository at this point in the history
|                 Method |     Mean |     Error |    StdDev | Ratio | RatioSD |
|----------------------- |---------:|----------:|----------:|------:|--------:|
| OriginalImplementation | 2.525 us | 0.0752 us | 0.2146 us |  1.00 |    0.00 |
|        Option1AndAHalf | 5.125 us | 0.1193 us | 0.3442 us |  2.04 |    0.23 |
|                Option2 | 5.342 us | 0.1613 us | 0.4680 us |  2.13 |    0.26 |
Magnus Mikkelsen committed Jun 29, 2020
1 parent 1563f8e commit 2a1fafa
Showing 5 changed files with 383 additions and 2 deletions.
35 changes: 35 additions & 0 deletions Src/BenchMarking/BenchMarkXeger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using Fare;

namespace BenchMarking
{
public class BenchMarkXeger
{
private const string pattern = ".";

[Benchmark(Baseline = true)]
public Xeger OriginalImplementation() => new Xeger(pattern);

[Benchmark]
public Fare.PR56Option1AndAHalf.Xeger Option1AndAHalf() => new Fare.PR56Option1AndAHalf.Xeger(pattern);

[Benchmark]
public Fare.PR56Option2.Xeger Option2() => new Fare.PR56Option2.Xeger(pattern);
}

public class Program
{
public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<BenchMarkXeger>();
}
}
}
16 changes: 16 additions & 0 deletions Src/BenchMarking/BenchMarking.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
</ItemGroup>

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

</Project>
12 changes: 10 additions & 2 deletions Src/Fare.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2020
# Visual Studio Version 16
VisualStudioVersion = 16.0.30204.135
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fare", "Fare\Fare.csproj", "{24033E0E-0304-4F06-94CD-6F6416ECA509}"
EndProject
@@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
..\Build.fsx = ..\Build.fsx
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchMarking", "BenchMarking\BenchMarking.csproj", "{EA6FAD38-1671-47A5-A8AA-C845B45FA31A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -33,6 +35,12 @@ Global
{20F5CC3B-9D71-472A-BD28-3A4C104A00E4}.Release|Any CPU.Build.0 = Release|Any CPU
{20F5CC3B-9D71-472A-BD28-3A4C104A00E4}.Verify|Any CPU.ActiveCfg = Verify|Any CPU
{20F5CC3B-9D71-472A-BD28-3A4C104A00E4}.Verify|Any CPU.Build.0 = Verify|Any CPU
{EA6FAD38-1671-47A5-A8AA-C845B45FA31A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EA6FAD38-1671-47A5-A8AA-C845B45FA31A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA6FAD38-1671-47A5-A8AA-C845B45FA31A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA6FAD38-1671-47A5-A8AA-C845B45FA31A}.Release|Any CPU.Build.0 = Release|Any CPU
{EA6FAD38-1671-47A5-A8AA-C845B45FA31A}.Verify|Any CPU.ActiveCfg = Debug|Any CPU
{EA6FAD38-1671-47A5-A8AA-C845B45FA31A}.Verify|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
154 changes: 154 additions & 0 deletions Src/Fare/XegerOption1AndAHalf.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Copyright 2009 Wilfred Springer
* http://github.com/moodmosaic/Fare/
* Original Java code:
* http://code.google.com/p/xeger/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;
using System.Text;

namespace Fare.PR56Option1AndAHalf
{
/// <summary>
/// An object that will generate text from a regular expression. In a way,
/// it's the opposite of a regular expression matcher: an instance of this class
/// will produce text that is guaranteed to match the regular expression passed in.
/// </summary>
public class Xeger
{
private const RegExpSyntaxOptions AllExceptAnyString = RegExpSyntaxOptions.All & ~RegExpSyntaxOptions.Anystring;

private static readonly Random globalRandom = new Random(GenerateGlobalSeed());

private readonly Automaton automaton;
private readonly Random random;

/// <summary>
/// Initializes a new instance of the <see cref="Xeger"/> class.
/// </summary>
/// <param name="regex">The regex.</param>
/// <param name="random">The random.</param>
public Xeger(string regex, Random random)
{
if (string.IsNullOrEmpty(regex))
{
throw new ArgumentNullException("regex");
}

if (random == null)
{
throw new ArgumentNullException("random");
}


regex = RemoveStartEndMarkers(regex);
this.automaton = new RegExp(regex, AllExceptAnyString).ToAutomaton();
this.random = random;
}

/// <summary>
/// Initializes a new instance of the <see cref="Xeger"/> class.
/// </summary>
/// <param name="regex">The regex.</param>
public Xeger(string regex)
: this(regex, new Random(GenerateSeed()))
{
}

private void AppendChoice(StringBuilder builder, Transition transition)
{
var c = (char) Xeger.GetRandomInt(transition.Min, transition.Max, random);
builder.Append(c);
}


/// <summary>
/// Generates a random String that is guaranteed to match the regular expression passed to the constructor.
/// </summary>
/// <returns></returns>
public string Generate()
{
var builder = new StringBuilder();
this.Generate(builder, automaton.Initial);
return builder.ToString();
}

private void Generate(StringBuilder builder, State state)
{
var transitions = state.GetSortedTransitions(true);
if (transitions.Count == 0)
{
if (!state.Accept)
{
throw new InvalidOperationException("state");
}

return;
}

int nroptions = state.Accept ? transitions.Count : transitions.Count - 1;
int option = Xeger.GetRandomInt(0, nroptions, random);
if (state.Accept && option == 0)
{
// 0 is considered stop.
return;
}

// Moving on to next transition.
Transition transition = transitions[option - (state.Accept ? 1 : 0)];
this.AppendChoice(builder, transition);
Generate(builder, transition.To);
}

private static int GenerateGlobalSeed()
{
return Guid.NewGuid().GetHashCode();
}

private static int GenerateSeed()
{
return globalRandom.Next();
}

/// <summary>
/// Generates a random number within the given bounds.
/// </summary>
/// <param name="min">The minimum number (inclusive).</param>
/// <param name="max">The maximum number (inclusive).</param>
/// <param name="random">The object used as the randomizer.</param>
/// <returns>A random number in the given range.</returns>
private static int GetRandomInt(int min, int max, Random random)
{
int maxForRandom = max - min + 1;
return random.Next(maxForRandom) + min;
}

private string RemoveStartEndMarkers(string regExp)
{
if (regExp.StartsWith("^"))
{
regExp = regExp.Substring(1);
}

if (regExp.EndsWith("$"))
{
regExp = regExp.Substring(0, regExp.Length - 1);
}

return regExp;
}
}
}
168 changes: 168 additions & 0 deletions Src/Fare/XegerOption2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* Copyright 2009 Wilfred Springer
* http://github.com/moodmosaic/Fare/
* Original Java code:
* http://code.google.com/p/xeger/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System;
using System.Text;

namespace Fare.PR56Option2
{
/// <summary>
/// An object that will generate text from a regular expression. In a way,
/// it's the opposite of a regular expression matcher: an instance of this class
/// will produce text that is guaranteed to match the regular expression passed in.
/// </summary>
public class Xeger
{
private const RegExpSyntaxOptions AllExceptAnyString = RegExpSyntaxOptions.All & ~RegExpSyntaxOptions.Anystring;

[ThreadStatic]
private static Random threadRandom;
private static readonly Random globalRandom = new Random(GenerateGlobalSeed());

private readonly Automaton automaton;
private readonly Random random;

/// <summary>
/// Initializes a new instance of the <see cref="Xeger"/> class.
/// </summary>
/// <param name="regex">The regex.</param>
/// <param name="random">The random.</param>
public Xeger(string regex, Random random)
{
if (string.IsNullOrEmpty(regex))
{
throw new ArgumentNullException("regex");
}

if (random == null)
{
throw new ArgumentNullException("random");
}


regex = RemoveStartEndMarkers(regex);
this.automaton = new RegExp(regex, AllExceptAnyString).ToAutomaton();
this.random = random;
}

/// <summary>
/// Initializes a new instance of the <see cref="Xeger"/> class.
/// </summary>
/// <param name="regex">The regex.</param>
public Xeger(string regex)
: this(regex, new Random(GenerateSeed()))
{
}

private void AppendChoice(StringBuilder builder, Transition transition)
{
var c = (char) Xeger.GetRandomInt(transition.Min, transition.Max, random);
builder.Append(c);
}


/// <summary>
/// Generates a random String that is guaranteed to match the regular expression passed to the constructor.
/// </summary>
/// <returns></returns>
public string Generate()
{
var builder = new StringBuilder();
this.Generate(builder, automaton.Initial);
return builder.ToString();
}

private void Generate(StringBuilder builder, State state)
{
var transitions = state.GetSortedTransitions(true);
if (transitions.Count == 0)
{
if (!state.Accept)
{
throw new InvalidOperationException("state");
}

return;
}

int nroptions = state.Accept ? transitions.Count : transitions.Count - 1;
int option = Xeger.GetRandomInt(0, nroptions, random);
if (state.Accept && option == 0)
{
// 0 is considered stop.
return;
}

// Moving on to next transition.
Transition transition = transitions[option - (state.Accept ? 1 : 0)];
this.AppendChoice(builder, transition);
Generate(builder, transition.To);
}

private static int GenerateGlobalSeed()
{
return Guid.NewGuid().GetHashCode();
}

private static int GenerateSeed()
{
Random rnd = threadRandom;
if (rnd == null)
{
int seed;
lock (globalRandom)
{
seed = globalRandom.Next();
}
rnd = new Random(seed);
threadRandom = rnd;
}

return rnd.Next();
}

/// <summary>
/// Generates a random number within the given bounds.
/// </summary>
/// <param name="min">The minimum number (inclusive).</param>
/// <param name="max">The maximum number (inclusive).</param>
/// <param name="random">The object used as the randomizer.</param>
/// <returns>A random number in the given range.</returns>
private static int GetRandomInt(int min, int max, Random random)
{
int maxForRandom = max - min + 1;
return random.Next(maxForRandom) + min;
}

private string RemoveStartEndMarkers(string regExp)
{
if (regExp.StartsWith("^"))
{
regExp = regExp.Substring(1);
}

if (regExp.EndsWith("$"))
{
regExp = regExp.Substring(0, regExp.Length - 1);
}

return regExp;
}
}
}

0 comments on commit 2a1fafa

Please sign in to comment.