Skip to content

Added support for empty case in random collections #18568

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

Merged
merged 8 commits into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions docs/release-notes/.FSharp.Core/10.0.100.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
### Fixed

### Added

### Changed

* Random functions support for zero element chosen/sampled ([PR #18568](https://github.com/dotnet/fsharp/pull/18568))

### Breaking Changes
130 changes: 71 additions & 59 deletions src/FSharp.Core/array.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2020,15 +2020,18 @@ module Array =
let inputLength = source.Length

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

let result = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked count
if count = 0 then
[||]
else
invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString
else
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]
for i = 0 to count - 1 do
let j = random.Next(0, inputLength)
result[i] <- source[j]

result
result

[<CompiledName("RandomChoicesBy")>]
let randomChoicesBy (randomizer: unit -> float) (count: int) (source: 'T array) : 'T array =
Expand All @@ -2040,15 +2043,18 @@ module Array =
let inputLength = source.Length

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

let result = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked count
if count = 0 then
[||]
else
invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString
else
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]
for i = 0 to count - 1 do
let j = Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength
result[i] <- source[j]

result
result

[<CompiledName("RandomChoices")>]
let randomChoices (count: int) (source: 'T array) : 'T array =
Expand All @@ -2065,35 +2071,38 @@ module Array =
let inputLength = source.Length

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

if count > inputLength then
invalidArg "count" (SR.GetString(SR.notEnoughElements))
if count = 0 then
[||]
else
invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString
else
if count > inputLength then
invalidArg "count" (SR.GetString(SR.notEnoughElements))

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

let setSize =
Microsoft.FSharp.Primitives.Basics.Random.getMaxSetSizeForSampling count
let setSize =
Microsoft.FSharp.Primitives.Basics.Random.getMaxSetSizeForSampling count

if inputLength <= setSize then
let pool = copy source
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 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)
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)
while not (selected.Add j) do
j <- random.Next(0, inputLength)

result[i] <- source[j]
result[i] <- source[j]

result
result

[<CompiledName("RandomSampleBy")>]
let randomSampleBy (randomizer: unit -> float) (count: int) (source: 'T array) : 'T array =
Expand All @@ -2105,39 +2114,42 @@ module Array =
let inputLength = source.Length

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

if count > inputLength then
invalidArg "count" (SR.GetString(SR.notEnoughElements))
if count = 0 then
[||]
else
invalidArg "source" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString
else
if count > inputLength then
invalidArg "count" (SR.GetString(SR.notEnoughElements))

let result = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked count
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
// 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
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)
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()
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
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
while not (selected.Add j) do
j <- Microsoft.FSharp.Primitives.Basics.Random.next randomizer 0 inputLength

result[i] <- source[j]
result[i] <- source[j]

result
result

[<CompiledName("RandomSample")>]
let randomSample (count: int) (source: 'T array) : 'T array =
Expand Down
12 changes: 6 additions & 6 deletions src/FSharp.Core/array.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -3282,7 +3282,7 @@ module Array =
/// <returns>An array of randomly selected elements from the input array.</returns>
///
/// <exception cref="T:System.ArgumentNullException">Thrown when the input array is null.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when the input array is empty.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when count is more than 0 and the input array is empty.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when count is less than 0.</exception>
///
/// <example id="randomChoices-1">
Expand All @@ -3306,7 +3306,7 @@ module Array =
///
/// <exception cref="T:System.ArgumentNullException">Thrown when the input array is null.</exception>
/// <exception cref="T:System.ArgumentNullException">Thrown when the random argument is null.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when the input array is empty.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when count is more than 0 and the input array is empty.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when count is less than 0.</exception>
///
/// <example id="randomChoicesWith-1">
Expand All @@ -3329,7 +3329,7 @@ module Array =
/// <returns>An array of randomly selected elements from the input array.</returns>
///
/// <exception cref="T:System.ArgumentNullException">Thrown when the input array is null.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when the input array is empty.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when count is more than 0 and the input array is empty.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when count is less than 0.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">Thrown when the randomizer function returns a value outside the range [0, 1).</exception>
///
Expand All @@ -3352,7 +3352,7 @@ module Array =
/// <returns>An array of randomly selected elements from the input array.</returns>
///
/// <exception cref="T:System.ArgumentNullException">Thrown when the input array is null.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when the input array is empty.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when count is more than 0 and the input array is empty.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when count is less than 0.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when count is greater than the length of the input array.</exception>
///
Expand All @@ -3377,7 +3377,7 @@ module Array =
///
/// <exception cref="T:System.ArgumentNullException">Thrown when the input array is null.</exception>
/// <exception cref="T:System.ArgumentNullException">Thrown when the random argument is null.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when the input array is empty.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when count is more than 0 and the input array is empty..</exception>
/// <exception cref="T:System.ArgumentException">Thrown when count is less than 0.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when count is greater than the length of the input array.</exception>
///
Expand All @@ -3401,7 +3401,7 @@ module Array =
/// <returns>An array of randomly selected elements from the input array.</returns>
///
/// <exception cref="T:System.ArgumentNullException">Thrown when the input array is null.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when the input array is empty.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when count is more than 0 and the input array is empty.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when count is less than 0.</exception>
/// <exception cref="T:System.ArgumentException">Thrown when count is greater than the length of the input array.</exception>
/// <exception cref="T:System.ArgumentOutOfRangeException">Thrown when the randomizer function returns a value outside the range [0, 1).</exception>
Expand Down
Loading