Skip to content

Commit c072b54

Browse files
authored
Merge pull request #18 from Dowlatabadi/RandomDrawItems
Randomly Drawn x Items Method Added (woithout placement using Fisher-…
2 parents cde6268 + c212626 commit c072b54

5 files changed

Lines changed: 67 additions & 0 deletions

File tree

src/RandomGen/Fluent/INumbers.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
using System;
2+
using System.Collections.Generic;
23

34
namespace RandomGen.Fluent
45
{
56
public interface INumbers : IFluentInterface
67
{
78
Func<byte> Bytes(byte min = byte.MinValue, byte max = byte.MaxValue);
89
Func<int> Integers(int min = 0, int max = 100);
10+
Func<IEnumerable<int>> IntegersDrawNoPlacement(int min = 0, int max = 100, int take = 1);
911
Func<uint> UnsignedIntegers(uint min = 0, uint max = 100);
1012
Func<long> Longs(long min = 0, long max = 100);
1113
IDouble Doubles();

src/RandomGen/Fluent/IRandom.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ public interface IRandom : IFluentInterface
7474
/// <param name="weights">Optional weights affecting the likelihood of a value being chosen. Same length as Enum values</param>
7575
Func<T> Enum<T>(IEnumerable<double> weights = null) where T : struct, IConvertible;
7676

77+
/// <summary>
78+
/// Returns a list of randomly drawn elements without placements
79+
/// </summary>
80+
/// <typeparam name="T"></typeparam>
81+
/// <param name="items"></param>
82+
/// <param name="take">The numbers of desired drawn elements</param>
83+
Func<IEnumerable<T>> ItemsNoPlacement<T>(IEnumerable<T> items, int take = 1);
84+
7785
/// <summary>
7886
/// Generates random country names
7987
/// Based on System.Globalisation

src/RandomGen/NumbersLink.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,32 @@ public Func<int> Integers(int min = 0, int max = 100)
3535
return () => random.Next(min, max);
3636
}
3737

38+
public Func<IEnumerable<int>> IntegersDrawNoPlacement(int min = 0, int max = 100, int take = 1)
39+
{
40+
if (min >= max)
41+
throw new ArgumentOutOfRangeException("min >= max");
42+
if (take > (max - min))
43+
throw new ArgumentOutOfRangeException("available < take");
44+
45+
var pool = Enumerable.Range(0, max - min).ToArray();
46+
var result = new List<int>();
47+
var random = this._genLink.CreateRandom();
48+
49+
//Modern Fisher–Yates shuffle with O(n) complexity
50+
for (int i = 0; i < take; i++)
51+
{
52+
var itemIndex = random.Next(min, max - i);
53+
54+
// swap taken index and current last
55+
var buffer = pool[itemIndex];
56+
pool[itemIndex] = pool[max - i - 1];
57+
pool[max - i - 1] = buffer;
58+
result.Add(buffer + min);
59+
}
60+
61+
return () => result;
62+
}
63+
3864
public Func<uint> UnsignedIntegers(uint min = 0, uint max = 100)
3965
{
4066
if (min >= max)

src/RandomGen/RandomLink.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,5 +99,13 @@ public Func<string> Countries()
9999

100100
return this.Items(data);
101101
}
102+
103+
public Func<IEnumerable<T>> ItemsNoPlacement<T>(IEnumerable<T> items, int take = 1)
104+
{
105+
var indexes = this.Numbers.IntegersDrawNoPlacement(0, items.Count(), take)();
106+
var result = indexes.Select(i => items.ElementAt(i));
107+
108+
return () => result;
109+
}
102110
}
103111
}

test/RandomGen.Tests/RandomTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Concurrent;
33
using System.Collections.Generic;
4+
using System.Diagnostics;
45
using System.Linq;
56
using System.Text;
67
using System.Threading;
@@ -219,6 +220,28 @@ public void RandomItems()
219220
Console.WriteLine(item);
220221
}
221222
}
223+
224+
[Fact]
225+
public void RandomItemsNoPlacement()
226+
{
227+
var take = 1000000;
228+
var random = new Random();
229+
var input = Enumerable.Range(0, 10000000).ToList();
230+
231+
var sw = Stopwatch.StartNew();
232+
var fisherYatesShuffle = Gen.Random.ItemsNoPlacement(input, take)().ToList();
233+
var fisherYatesTime = sw.Elapsed.TotalSeconds;
234+
235+
sw.Restart();
236+
var normal = input.OrderBy(x => random.NextDouble()).Take(take).ToList();
237+
var normalTime = sw.Elapsed.TotalSeconds;
238+
239+
//check for non duplicate items
240+
Assert.False(fisherYatesShuffle.GroupBy(x => x).Where(x => x.Count() > 1).Any());
241+
Assert.False(normal.GroupBy(x => x).Where(x => x.Count() > 1).Any());
242+
243+
Console.WriteLine($"For large lists Fisher Yates shuffle should outperform random shuffling: {fisherYatesTime}<{normalTime}");
244+
}
222245

223246
[Fact]
224247
public void ListLikelihoodWeightsWorks()

0 commit comments

Comments
 (0)