Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FS-1135 implementation - random functions for collections #17277

Merged
merged 29 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
eb0cbb1
Random functions: old version rebased
Lanayx May 29, 2024
a7986bd
Random functions: Rename functions according to the RFC
Lanayx May 30, 2024
ac6d5aa
Random functions: More naming refactoring and documentation
Lanayx May 30, 2024
aebc5e6
Random functions: More documentation coments
Lanayx May 31, 2024
2f27590
Random functions: Added randomShuffleInPlace functions and docs
Lanayx May 31, 2024
938646a
Random functions: refactoring
Lanayx May 31, 2024
cae6305
Added null checks
Lanayx Jun 1, 2024
3889a45
Added *by functions
Lanayx Jun 2, 2024
73884e5
Random functions: Array random functions tests
Lanayx Jun 3, 2024
6544ca4
Added tests for lists and seqs
Lanayx Jun 4, 2024
dfc6692
Random functions: addded *By tests to arrays and sequences
Lanayx Jun 4, 2024
957bcfa
Random functions: addded *By tests to lists
Lanayx Jun 4, 2024
4488f23
Random functions: try fix CI
Lanayx Jun 4, 2024
e6e059a
Merge branch 'dotnet:main' into master
Lanayx Jun 5, 2024
a8b9a7d
Random functions: review fixes
Lanayx Jun 5, 2024
5b4dbe1
Try fix CI
Lanayx Jun 5, 2024
82860d8
Changed thread local implementation to thread static for performance …
Lanayx Jun 8, 2024
3826743
PR review fix
Lanayx Jun 8, 2024
887a32f
PR fix
Lanayx Jun 11, 2024
a9001fe
PR review fix
Lanayx Jun 11, 2024
c5943be
Reverted HashSet constructor improvement since not netstandard2.0 com…
Lanayx Jun 12, 2024
17e0cfb
Fix formatting
Lanayx Jun 12, 2024
757e00c
Merge branch 'main' into master
psfinaki Jun 13, 2024
2334d18
Fixed nan case for randomizer function
Lanayx Jun 13, 2024
ccee921
PR review fixes
Lanayx Jun 19, 2024
c56e5e2
Merge branch 'main' into master
psfinaki Jun 21, 2024
26785f1
Merge branch 'main' into master
psfinaki Jun 25, 2024
f832333
PR review changes
Lanayx Jun 26, 2024
3f35e7a
Fixed input length check logic for sample
Lanayx Jun 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/release-notes/.FSharp.Core/8.0.400.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### Added

* `Random functions for collections` ([RFC #1135](https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1135-random-functions-for-collections.md), [PR #17277](https://github.com/dotnet/fsharp/pull/17277))

### Changed

* Cache delegate in query extensions. ([PR #17130](https://github.com/dotnet/fsharp/pull/17130))
Expand Down
6 changes: 6 additions & 0 deletions src/FSharp.Core/FSharp.Core.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@
<Compile Include="prim-types.fs">
<Link>Primitives/prim-types.fs</Link>
</Compile>
<Compile Include="Random.fsi">
<Link>Random/Random.fsi</Link>
</Compile>
<Compile Include="Random.fs">
<Link>Random/Random.fs</Link>
</Compile>
<Compile Include="local.fsi">
<Link>Collections/local.fsi</Link>
</Compile>
Expand Down
23 changes: 23 additions & 0 deletions src/FSharp.Core/Random.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

namespace Microsoft.FSharp.Core

open System
open System.Threading

[<AbstractClass; Sealed>]
type internal ThreadSafeRandom() =
[<DefaultValue>]
static val mutable private globalRandom: Random

[<DefaultValue>]
static val mutable private localRandom: ThreadLocal<Random>

static do ThreadSafeRandom.globalRandom <- Random()

static do
ThreadSafeRandom.localRandom <-
new ThreadLocal<Random>(fun () ->
lock ThreadSafeRandom.globalRandom (fun () -> Random(ThreadSafeRandom.globalRandom.Next())))
// Don't pass the returned Random object between threads
static member Shared = ThreadSafeRandom.localRandom.Value
psfinaki marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 7 additions & 0 deletions src/FSharp.Core/Random.fsi
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Microsoft.FSharp.Core

open System

[<AbstractClass; Sealed>]
type internal ThreadSafeRandom =
Lanayx marked this conversation as resolved.
Show resolved Hide resolved
static member Shared: Random
206 changes: 206 additions & 0 deletions src/FSharp.Core/array.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1936,6 +1936,212 @@ module Array =

result

[<CompiledName("RandomShuffleWith")>]
let randomShuffleWith (random: Random) (source: 'T array) : 'T array =
checkNonNull "random" random
checkNonNull "source" source
psfinaki marked this conversation as resolved.
Show resolved Hide resolved

let result = copy source

Microsoft.FSharp.Primitives.Basics.Random.shuffleArrayInPlaceWith random result

result

[<CompiledName("RandomShuffleBy")>]
let randomShuffleBy (randomizer: unit -> float) (source: 'T array) : 'T array =
checkNonNull "source" source

let result = copy source

Microsoft.FSharp.Primitives.Basics.Random.shuffleArrayInPlaceBy randomizer result

result

[<CompiledName("RandomShuffle")>]
let randomShuffle (source: 'T array) : 'T array =
randomShuffleWith ThreadSafeRandom.Shared source

[<CompiledName("RandomShuffleInPlaceWith")>]
let randomShuffleInPlaceWith (random: Random) (source: 'T array) =
checkNonNull "random" random
checkNonNull "source" source

Microsoft.FSharp.Primitives.Basics.Random.shuffleArrayInPlaceWith random source

[<CompiledName("RandomShuffleInPlaceBy")>]
let randomShuffleInPlaceBy (randomizer: unit -> float) (source: 'T array) =
checkNonNull "source" source

Microsoft.FSharp.Primitives.Basics.Random.shuffleArrayInPlaceBy randomizer source

[<CompiledName("RandomShuffleInPlace")>]
let randomShuffleInPlace (source: 'T array) =
randomShuffleInPlaceWith ThreadSafeRandom.Shared source

[<CompiledName("RandomChoiceWith")>]
let randomChoiceWith (random: Random) (source: 'T array) : 'T =
checkNonNull "random" random
checkNonNull "source" source

let inputLength = source.Length

if inputLength = 0 then
invalidArg "source" LanguagePrimitives.ErrorStrings.InputArrayEmptyString

let i = random.Next(0, inputLength)
source[i]

[<CompiledName("RandomChoiceBy")>]
let randomChoiceBy (randomizer: unit -> float) (source: 'T array) : 'T =
checkNonNull "source" source

let inputLength = source.Length

if inputLength = 0 then
invalidArg "source" LanguagePrimitives.ErrorStrings.InputArrayEmptyString

let i = Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength
source[i]

[<CompiledName("RandomChoice")>]
let randomChoice (source: 'T array) : 'T =
randomChoiceWith ThreadSafeRandom.Shared source

[<CompiledName("RandomChoicesWith")>]
let randomChoicesWith (random: Random) (count: int) (source: 'T array) : 'T array =
checkNonNull "random" random
checkNonNull "source" source

if count < 0 then
invalidArgInputMustBeNonNegative "count" count

let inputLength = source.Length

if inputLength = 0 then
invalidArg "source" LanguagePrimitives.ErrorStrings.InputArrayEmptyString

let result = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked count

for i = 0 to count - 1 do
let j = random.Next(0, inputLength)
result[i] <- source[j]

result

[<CompiledName("RandomChoicesBy")>]
let randomChoicesBy (randomizer: unit -> float) (count: int) (source: 'T array) : 'T array =
checkNonNull "source" source

if count < 0 then
invalidArgInputMustBeNonNegative "count" count

let inputLength = source.Length

if inputLength = 0 then
invalidArg "source" LanguagePrimitives.ErrorStrings.InputArrayEmptyString

let result = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked count

for i = 0 to count - 1 do
let j = Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength
result[i] <- source[j]

result

[<CompiledName("RandomChoices")>]
let randomChoices (count: int) (source: 'T array) : 'T array =
randomChoicesWith ThreadSafeRandom.Shared count source

[<CompiledName("RandomSampleWith")>]
let randomSampleWith (random: Random) (count: int) (source: 'T array) : 'T array =
checkNonNull "random" random
checkNonNull "source" source

if count < 0 then
invalidArgInputMustBeNonNegative "count" count

let inputLength = source.Length

if inputLength = 0 then
invalidArg "source" LanguagePrimitives.ErrorStrings.InputArrayEmptyString

if count > inputLength then
invalidArgOutOfRange "count" count "source.Length" inputLength

let result = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked count

// algorithm taken from https://github.com/python/cpython/blob/69b3e8ea569faabccd74036e3d0e5ec7c0c62a20/Lib/random.py#L363-L456
Lanayx marked this conversation as resolved.
Show resolved Hide resolved
let setSize =
Microsoft.FSharp.Primitives.Basics.Random.getMaxSetSizeForSampling count

if inputLength <= setSize then
let pool = copy source

for i = 0 to count - 1 do
let j = random.Next(0, inputLength - i)
result[i] <- pool[j]
pool[j] <- pool[inputLength - i - 1]
else
let selected = HashSet()

for i = 0 to count - 1 do
let mutable j = random.Next(0, inputLength)

while not (selected.Add j) do
j <- random.Next(0, inputLength)

result[i] <- source[j]

result

[<CompiledName("RandomSampleBy")>]
let randomSampleBy (randomizer: unit -> float) (count: int) (source: 'T array) : 'T array =
checkNonNull "source" source

if count < 0 then
invalidArgInputMustBeNonNegative "count" count

let inputLength = source.Length

if inputLength = 0 then
invalidArg "source" LanguagePrimitives.ErrorStrings.InputArrayEmptyString

if count >= inputLength then
invalidArgOutOfRange "count" count "source.Length" inputLength

let result = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked count

// algorithm taken from https://github.com/python/cpython/blob/69b3e8ea569faabccd74036e3d0e5ec7c0c62a20/Lib/random.py#L363-L456
let setSize =
Microsoft.FSharp.Primitives.Basics.Random.getMaxSetSizeForSampling count

if inputLength <= setSize then
let pool = copy source

for i = 0 to count - 1 do
let j =
Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 (inputLength - i)

result[i] <- pool[j]
pool[j] <- pool[inputLength - i - 1]
else
let selected = HashSet()

for i = 0 to count - 1 do
let mutable j =
Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength

while not (selected.Add j) do
j <- Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength

result[i] <- source[j]

result

[<CompiledName("RandomSample")>]
let randomSample (count: int) (source: 'T array) : 'T array =
randomSampleWith ThreadSafeRandom.Shared count source

module Parallel =
open System.Threading
open System.Threading.Tasks
Expand Down
Loading
Loading