Skip to content

Instantly share code, notes, and snippets.

@brianrourkeboll
Last active June 19, 2024 22:36
Show Gist options
  • Save brianrourkeboll/9935b6aa35a2227610a4251b38404729 to your computer and use it in GitHub Desktop.
Save brianrourkeboll/9935b6aa35a2227610a4251b38404729 to your computer and use it in GitHub Desktop.
open System
open System.Collections.Generic
open System.Runtime.CompilerServices
open System.Security.Cryptography
[<Sealed>]
type internal ThreadSafeRandom() =
inherit Random()
[<DefaultValue>]
[<ThreadStatic>]
static val mutable private random: Random
[<MethodImpl(MethodImplOptions.NoInlining)>]
static member private Create() =
ThreadSafeRandom.random <- Random()
ThreadSafeRandom.random
static member private LocalRandom =
match ThreadSafeRandom.random with
| null -> ThreadSafeRandom.Create()
| random -> random
override _.Next() = ThreadSafeRandom.LocalRandom.Next()
override _.Next maxValue = ThreadSafeRandom.LocalRandom.Next maxValue
override _.Next(minValue, maxValue) = ThreadSafeRandom.LocalRandom.Next(minValue, maxValue)
override _.NextDouble() = ThreadSafeRandom.LocalRandom.NextDouble()
override _.NextBytes(buffer: byte array) = ThreadSafeRandom.LocalRandom.NextBytes buffer
override _.Sample() = raise (NotSupportedException())
[<AutoOpen>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module internal ThreadSafeRandom =
// Avoid the static init check overhead on each access
// that would happen if this were a static member val in a class.
// See: https://github.com/dotnet/fsharp/issues/6454
let private shared = ThreadSafeRandom()
type Random with
static member internal Shared: Random = shared
[<RequireQualifiedAccess>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Random =
let nextInt () = Random.Shared.Next()
let int maxValue = Random.Shared.Next maxValue
let intInRange minValue maxValue = Random.Shared.Next(minValue, maxValue)
let nextFloat () = Random.Shared.NextDouble()
let bytes count =
if count < 0 then invalidArg (nameof count) "…"
let bytes = Array.zeroCreate count
Random.Shared.NextBytes bytes
bytes
let fill (buffer: byte array) = Random.Shared.NextBytes buffer
module Array =
let inline shuffle ([<InlineIfLambda>] randomizer) (array: 'T array) =
if isNull array then
nullArg (nameof array)
let array = Unchecked.unbox<'T array> (array.Clone())
for i in array.Length - 1 .. -1 .. 1 do
let j = randomizer (i + 1)
if j < 0 || i < j then invalidArg (nameof randomizer) "…"
let temp = array[i]
array[i] <- array[j]
array[j] <- temp
array
let choice randomizer (array: 'T array) =
if isNull array then
nullArg (nameof array)
if array.Length = 0 then
invalidArg (nameof array) "…"
let i = randomizer array.Length
if i < 0 then
invalidArg (nameof randomizer) "…"
if array.Length <= i then
invalidArg (nameof randomizer) "…"
array[i]
let inline choices ([<InlineIfLambda>] randomizer) count (array: 'T array) =
if isNull array then
nullArg (nameof array)
if count < 0 then
invalidArg (nameof count) "…"
if array.Length < count then
invalidArg (nameof count) "…"
if array.Length = 0 then
invalidArg (nameof array) "…"
[|
for _ in 1 .. count do
let i = randomizer array.Length
if i < 0 then
invalidArg (nameof randomizer) "…"
if array.Length <= i then
invalidArg (nameof randomizer) "…"
yield array[i]
|]
let inline sample ([<InlineIfLambda>] randomizer) count (array: 'T array) =
if isNull array then
nullArg (nameof array)
if count < 0 then
invalidArg (nameof count) "…"
if array.Length = 0 then
invalidArg (nameof array) "…"
// TODO: Switch between impls depending on count.
let seen = HashSet()
[|
for _ in 0 .. count do
let rec nextInt () =
let i = randomizer array.Length
if i < 0 then
invalidArg (nameof randomizer) "…"
if array.Length <= i then
invalidArg (nameof randomizer) "…"
if seen.Add i then i else nextInt ()
let i = nextInt ()
yield array[i]
|]
module WithRandomModule =
// Using the Random module.
let shuffled = [|1..100|] |> Array.shuffle Random.int
let choice = [|1..100|] |> Array.choice Random.int
let choices = [|1..100|] |> Array.choices Random.int 33
let sample = [|1..100|] |> Array.sample Random.int 33
module NoRandomModule =
// Directly calling Random.Shared.Next.
let shuffled = [|1..100|] |> Array.shuffle Random.Shared.Next
let choice = [|1..100|] |> Array.choice Random.Shared.Next
let choices = [|1..100|] |> Array.choices Random.Shared.Next 33
let sample = [|1..100|] |> Array.sample Random.Shared.Next 33
module RandomNumberGenerator =
// Directly calling System.Security.Cryptography.RandomNumberGenerator.GetInt32.
let shuffled = [|1..100|] |> Array.shuffle RandomNumberGenerator.GetInt32
let choice = [|1..100|] |> Array.choice RandomNumberGenerator.GetInt32
let choices = [|1..100|] |> Array.choices RandomNumberGenerator.GetInt32 33
let sample = [|1..100|] |> Array.sample RandomNumberGenerator.GetInt32 33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment