forked from moodmosaic/Fare
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A couple of implementations tryed out. Benchmarking:
| 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.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |