Skip to content

Commit d23d219

Browse files
committed
Add shims for stdlib random
Initial random api Use C syscall for I/O 1. Fixed an issue where integers would would result in an infinite loop if they were unsigned, or signed integers always returning negative numbers. 2. Fixed an issue with Bool initialization Add shuffle functions Add documentation to Random API Fix a few typos within the documentation Fixes more typos Also states that the range for floating points is from 0 to 1 inclusive Update API to reflect mailing list discussions Remove unnecessary import Make sure not to return upperBound on Range Use SecRandomCopyBytes on older macOS Update API to match mailing list discussion, add tests Added pick(_:) to collection Added random(in:using:) to Randomizable Added tests Fix typo in Randomizable documentation Rename pick to sampling Move sampling below random Update docs Use new Libc naming Fix Random.swift with new Libc naming Remove sampling gybify signed integer creation Make FloatingPoint.random exclusive Refactor {Closed}Range.random Fix FloatingPoint initialization Precondition getting a random number from range Fix some doc typos Make .random a function Update API to reflect discussion Make .random a function Remove .random() in favor of .random(in:) for all numeric types Fix compile errors Clean up _stdlib_random Cleanup around API Remove `.random()` requirement from `Collection` Use generators Optimize shuffle() Thread safety for /dev/urandom Remove {Closed}Range<BinaryFloatingPoint>.random() Add Collection random requirement Refactor _stdlib_random Remove whitespace changes Clean linux shim Add shuffle and more tests Provide finishing tests and suggestions Remove refs to Countable ranges Revert to checking if T is > UInt64
1 parent 123cde5 commit d23d219

File tree

13 files changed

+670
-0
lines changed

13 files changed

+670
-0
lines changed

stdlib/public/SwiftShims/LibcShims.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,10 @@ __swift_uint32_t _stdlib_cxx11_mt19937(void);
149149
SWIFT_RUNTIME_STDLIB_INTERNAL
150150
__swift_uint32_t _stdlib_cxx11_mt19937_uniform(__swift_uint32_t upper_bound);
151151

152+
// Random number for stdlib
153+
SWIFT_RUNTIME_STDLIB_INTERNAL
154+
void _stdlib_random(void *buf, __swift_size_t nbytes);
155+
152156
// Math library functions
153157
static inline SWIFT_ALWAYS_INLINE
154158
float _stdlib_remainderf(float _self, float _other) {

stdlib/public/core/Bool.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,18 @@ public struct Bool {
8686
public init(_ value: Bool) {
8787
self = value
8888
}
89+
90+
/// Returns a random Boolean
91+
///
92+
/// - Parameter generator: The random number generator to use when getting a
93+
/// random Boolean.
94+
/// - Returns: A random Boolean.
95+
@_inlineable
96+
public static func random(
97+
using generator: RandomNumberGenerator = Random.default
98+
) -> Bool {
99+
return generator.next() % 2 == 0
100+
}
89101
}
90102

91103
extension Bool : _ExpressibleByBuiltinBooleanLiteral, ExpressibleByBooleanLiteral {

stdlib/public/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ set(SWIFTLIB_ESSENTIAL
9797
Policy.swift
9898
PrefixWhile.swift
9999
Print.swift
100+
Random.swift
100101
RandomAccessCollection.swift
101102
Range.swift
102103
RangeReplaceableCollection.swift
@@ -193,6 +194,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
193194
list(APPEND swift_core_link_flags "-all_load")
194195
list(APPEND swift_core_framework_depends Foundation)
195196
list(APPEND swift_core_framework_depends CoreFoundation)
197+
list(APPEND swift_core_framework_depends Security)
196198
list(APPEND swift_core_private_link_libraries icucore)
197199
else()
198200
# With the GNU linker the equivalent of -all_load is to tell the linker

stdlib/public/core/ClosedRange.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,57 @@ extension ClosedRange where Bound: Strideable, Bound.Stride : SignedInteger {
480480
}
481481
}
482482

483+
extension ClosedRange
484+
where Bound : FixedWidthInteger,
485+
Bound.Magnitude : UnsignedInteger {
486+
487+
/// Returns a random element from this collection.
488+
///
489+
/// - Parameter generator: The random number generator to use when getting
490+
/// a random element.
491+
/// - Returns: A random element from this collection.
492+
///
493+
/// A good example of this is getting a random greeting from an array:
494+
///
495+
/// let greetings = ["hi", "hey", "hello", "hola"]
496+
/// let randomGreeting = greetings.random()
497+
///
498+
/// If the collection is empty, the value of this function is `nil`.
499+
///
500+
/// let numbers = [10, 20, 30, 40, 50]
501+
/// if let randomNumber = numbers.random() {
502+
/// print(randomNumber)
503+
/// }
504+
/// // Could print "20", perhaps
505+
@_inlineable
506+
public func random(
507+
using generator: RandomNumberGenerator = Random.default
508+
) -> Element? {
509+
let isLowerNegative = Bound.isSigned && lowerBound < 0
510+
let sameSign = !Bound.isSigned || isLowerNegative == (upperBound < 0)
511+
var delta: Bound.Magnitude
512+
if isLowerNegative {
513+
delta = sameSign
514+
? lowerBound.magnitude - upperBound.magnitude
515+
: lowerBound.magnitude + upperBound.magnitude
516+
} else {
517+
delta = upperBound.magnitude - lowerBound.magnitude
518+
}
519+
if delta == Bound.Magnitude.max {
520+
return Bound(truncatingIfNeeded: generator.next() as Bound.Magnitude)
521+
}
522+
delta += 1
523+
let randomMagnitude = generator.next(upperBound: delta)
524+
if sameSign {
525+
return lowerBound + Bound(randomMagnitude)
526+
} else {
527+
return Bound.isSigned && randomMagnitude <= upperBound.magnitude
528+
? Bound(randomMagnitude)
529+
: 0 - Bound(randomMagnitude - upperBound.magnitude)
530+
}
531+
}
532+
}
533+
483534
extension ClosedRange {
484535
@inlinable
485536
public func overlaps(_ other: ClosedRange<Bound>) -> Bool {

stdlib/public/core/Collection.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,38 @@ extension Collection {
10161016
return count
10171017
}
10181018

1019+
/// Returns a random element from this collection.
1020+
///
1021+
/// - Parameter generator: The random number generator to use when getting
1022+
/// a random element.
1023+
/// - Returns: A random element from this collection.
1024+
///
1025+
/// A good example of this is getting a random greeting from an array:
1026+
///
1027+
/// let greetings = ["hi", "hey", "hello", "hola"]
1028+
/// let randomGreeting = greetings.random()
1029+
///
1030+
/// If the collection is empty, the value of this function is `nil`.
1031+
///
1032+
/// let numbers = [10, 20, 30, 40, 50]
1033+
/// if let randomNumber = numbers.random() {
1034+
/// print(randomNumber)
1035+
/// }
1036+
/// // Could print "20", perhaps
1037+
@_inlineable
1038+
public func random(
1039+
using generator: RandomNumberGenerator = Random.default
1040+
) -> Element? {
1041+
guard !isEmpty else { return nil }
1042+
let random = generator.next(upperBound: UInt(self.count))
1043+
let index = self.index(
1044+
self.startIndex,
1045+
offsetBy: IndexDistance(random),
1046+
limitedBy: self.endIndex
1047+
)
1048+
return self[index]
1049+
}
1050+
10191051
/// Do not use this method directly; call advanced(by: n) instead.
10201052
@inlinable
10211053
@inline(__always)

stdlib/public/core/CollectionAlgorithms.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,50 @@ extension MutableCollection where Self : BidirectionalCollection {
367367
}
368368
}
369369

370+
//===----------------------------------------------------------------------===//
371+
// shuffled()
372+
//===----------------------------------------------------------------------===//
373+
374+
extension Sequence {
375+
/// Returns the elements of the sequence, shuffled.
376+
///
377+
/// - Parameter generator: The random number generator to use when shuffling
378+
/// the sequence.
379+
/// - Returns: A shuffled array of this sequence's elements.
380+
@_inlineable
381+
public func shuffled(
382+
using generator: RandomNumberGenerator = Random.default
383+
) -> [Element] {
384+
var result = ContiguousArray(self)
385+
result.shuffle(using: generator)
386+
return Array(result)
387+
}
388+
}
389+
390+
extension MutableCollection {
391+
/// Shuffles the collection in place.
392+
///
393+
/// - Parameter generator: The random number generator to use when shuffling
394+
/// the collection.
395+
@_inlineable
396+
public mutating func shuffle(
397+
using generator: RandomNumberGenerator = Random.default
398+
) {
399+
guard count > 1 else { return }
400+
var amount = count
401+
var currentIndex = startIndex
402+
while amount > 1 {
403+
let random = generator.next(upperBound: UInt(amount))
404+
amount -= 1
405+
swapAt(
406+
currentIndex,
407+
index(currentIndex, offsetBy: numericCast(random))
408+
)
409+
formIndex(after: &currentIndex)
410+
}
411+
}
412+
}
413+
370414
//===----------------------------------------------------------------------===//
371415
// sorted()/sort()
372416
//===----------------------------------------------------------------------===//

stdlib/public/core/FloatingPoint.swift.gyb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2399,6 +2399,53 @@ extension BinaryFloatingPoint {
23992399
*/
24002400
}
24012401

2402+
% for Range in ['Range', 'ClosedRange']:
2403+
2404+
extension BinaryFloatingPoint
2405+
where Self.RawSignificand : FixedWidthInteger,
2406+
Self.RawSignificand.Stride : SignedInteger & FixedWidthInteger,
2407+
Self.RawSignificand.Magnitude : UnsignedInteger {
2408+
2409+
/// Returns a random representation of this floating point within the range.
2410+
///
2411+
/// - Parameter range: A ${Range} to determine the bounds to get a random value
2412+
/// from.
2413+
/// - Parameter generator: The random number generator to use when getting
2414+
/// the random floating point.
2415+
/// - Returns: A random representation of this floating point.
2416+
@_inlineable
2417+
public static func random(
2418+
in range: ${Range}<Self>,
2419+
using generator: RandomNumberGenerator = Random.default
2420+
) -> Self {
2421+
% if 'Closed' not in Range:
2422+
_precondition(
2423+
range.lowerBound != range.upperBound,
2424+
"Can't get random value with lowerBound == upperBound"
2425+
)
2426+
% end
2427+
let delta = range.upperBound - range.lowerBound
2428+
let maxSignificand: Self.RawSignificand = 1 << Self.significandBitCount
2429+
% if 'Closed' not in Range:
2430+
let rand: Self.RawSignificand = generator.next(upperBound: maxSignificand)
2431+
% else:
2432+
let rand: Self.RawSignificand = generator.next(upperBound: maxSignificand + 1)
2433+
if rand == maxSignificand {
2434+
return range.upperBound
2435+
}
2436+
% end
2437+
let unitRandom = Self.init(
2438+
sign: .plus,
2439+
exponentBitPattern: (1 as Self).exponentBitPattern,
2440+
significandBitPattern: rand
2441+
) - 1
2442+
return delta * unitRandom + range.lowerBound
2443+
}
2444+
2445+
}
2446+
2447+
% end
2448+
24022449
/// Returns the absolute value of `x`.
24032450
@inlinable // FIXME(sil-serialize-all)
24042451
@_transparent

stdlib/public/core/GroupInfo.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@
176176
"Policy.swift",
177177
"Print.swift",
178178
"REPL.swift",
179+
"Random.swift",
179180
"Runtime.swift",
180181
"RuntimeFunctionCounters.swift",
181182
"Shims.swift",

stdlib/public/core/Integers.swift.gyb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2530,6 +2530,36 @@ ${assignmentOperatorComment(x.operator, False)}
25302530
% end
25312531
}
25322532
2533+
% for Range in ['Range', 'ClosedRange']:
2534+
2535+
extension FixedWidthInteger
2536+
where Self.Stride : SignedInteger,
2537+
Self.Magnitude : UnsignedInteger {
2538+
2539+
/// Returns a random representation of this integer within the range.
2540+
///
2541+
/// - Parameter range: A ${Range} to determine the bounds to get a random value
2542+
/// from.
2543+
/// - Parameter generator: The random number generator to use when getting
2544+
/// the random integer.
2545+
/// - Returns: A random representation of this integer.
2546+
@_inlineable
2547+
public static func random(
2548+
in range: ${Range}<Self>,
2549+
using generator: RandomNumberGenerator = Random.default
2550+
) -> Self {
2551+
% if 'Closed' not in Range:
2552+
_precondition(
2553+
range.lowerBound != range.upperBound,
2554+
"Can't get random value with lowerBound == upperBound"
2555+
)
2556+
% end
2557+
return range.random(using: generator)!
2558+
}
2559+
}
2560+
2561+
% end
2562+
25332563
//===----------------------------------------------------------------------===//
25342564
//===--- Operators on FixedWidthInteger -----------------------------------===//
25352565
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)