-
Notifications
You must be signed in to change notification settings - Fork 14
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
Move random(inRange:)
to Collection
extension
#97
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love the change to signed integer.
|
||
import Foundation | ||
|
||
public extension CountableClosedRange where Bound: SignedInteger { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question here, does this impose any limitations on the stride then? can there be countableclosedrange of stride 2? from 0...4?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PS this is really nice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It hasn't been tested with different strides. I will address that before merging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, if there can indeed be different strides, then this is kind of worrying...we then need to make sure that the random output falls on the stride boundary, right? am I thinking about this right? 0...4 with stride 2 only had 0,2,4 as valid values.
}) | ||
|
||
guard let rngUpperBound = capturedRNGUpperBound, rngUpperBound > 0 else { | ||
XCTAssert(false, "Expected that RNG function upper bound \(capturedRNGUpperBound) is non-nil and positive.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather we translated some items in the guard into a more readable assertion.
ie
XCTAssert(rngUpperBound > 0, "Upper bound is less than zero, expected more than zero")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, per guard style guide, we should not use them as control flow, but only for early return. In addition, i dont think if lets are better in tests.
|
||
for mockRNGValue in 0..<rngUpperBound { | ||
let sample = testRange.ip_random(withUniformRNGFunction: { upperBound in | ||
XCTAssertEqual(upperBound, rngUpperBound, "Expected RNG function to be called with a consistent upper bound each time." ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
im slightly confused as to why this needs to be in a loop. mockRNGValue is not used in any assertion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am returning mockRNGValue
as the output of the mock RNG function. It's a stand-in for a possible value that arc4random would output. In other words, it's an input for ip_random
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, what im saying is that calling it in a loop doesn't actually test anything different.
|
||
class CountableClosedRangeExtensionTests: XCTestCase { | ||
|
||
// This test is complex. Here is an explanation as to why: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
totallly agree with the reasoning outlined in these comments, but I think we need to pare it down a little :D
@@ -0,0 +1,25 @@ | |||
// | |||
// CountableClosedRange+Extensions.swift |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like Ranges deserve their own home under StandardLibrary. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree.
@brightredchilli So in exploring the variable stride issue, I've come to the realization that (I think) |
@andrewdolce - it sounds like there is more work to be done there, maybe we can open an issue for that and get these guys checked in? |
Generated by 🚫 danger |
5543028
to
dd107d0
Compare
random(inRange:)
to CountableClosedRange
extensionrandom(inRange:)
to Collection
extension
@brightredchilli @paulrolfe I updated this finally. Could you guys give it another look? I'd like Paul to weigh in on the UnsignedInteger extension change, since it potentially impacts BMAP. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would advocate for not testing, but otherwise this is a good to go for me.
|
||
class RandomizableUnsignedIntegerTests: XCTestCase { | ||
func testRandom() { | ||
let _: UInt8 = UInt8.ip_random() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why let _? isn't _ = UInt8.ip_random()
enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, I'll fix.
|
||
public extension Collection { | ||
public func ip_random() -> Self.Iterator.Element { | ||
return ip_random(withUniformRNGFunction: arc4random_uniform) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we ever change this? if not can we just combine this and the below function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can remove this. The only purpose of it was to let the tests specify a mock RNG function.
|
||
class CollectionRandomTests: XCTestCase { | ||
|
||
// This test is complex. Here is an explanation as to why: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do think these tests are quite comprehensive but for the reasons that you have mentioned, i kind of think that we should not include them...they seem unwieldy to update and seem to be dancing around the 'actual' random test.
Fwiw, I ran this code:
var array = repeatElement(0, count: Int(UInt8.max)).ip_toArray()
for _ in 0...1000000 {
let index = UInt8.ip_random()
array[Int(index)] = array[Int(index)] + 1
}
print(array)
and got
[3927, 3857, 3833, 3904, 3933, 3932, 3956, 3889, 4012, 3949, 3924, 3888, 3871, 3890, 3891, 3900, 4003, 3945, 3804, 3898, 3934, 4019, 3976, 4001, 3885, 3893, 3940, 4007, 3872, 4012, 3954, 3971, 3829, 4060, 3953, 3850, 4010, 3873, 3963, 3979, 4003, 4006, 3922, 3836, 3893, 3958, 3799, 3873, 3894, 3976, 3840, 3890, 3964, 3916, 3917, 3978, 3918, 3816, 3925, 3880, 3974, 3914, 4006, 3945, 3748, 3888, 4020, 3876, 3981, 3994, 3941, 3809, 3962, 3929, 3922, 3854, 3928, 3940, 3978, 3930, 3912, 3864, 3818, 3983, 3895, 3869, 3859, 3972, 3972, 3905, 3948, 3992, 4049, 3936, 3914, 3895, 3931, 3985, 3882, 3895, 3926, 4041, 3865, 4000, 3965, 3987, 3853, 3987, 4011, 3823, 3815, 3902, 3968, 3940, 3818, 3866, 4001, 3974, 3866, 3980, 3952, 4053, 3890, 4031, 3896, 3843, 3857, 3893, 3905, 3967, 4091, 4035, 3918, 3881, 3826, 3955, 3898, 3896, 3854, 3966, 3854, 4006, 4012, 3878, 3957, 3914, 3922, 3934, 3885, 4037, 3962, 3937, 3848, 3920, 3834, 3860, 3859, 3858, 3966, 3900, 3948, 3929, 3856, 3925, 3824, 3888, 3934, 3848, 3980, 3935, 3942, 3923, 3934, 3871, 3886, 3867, 3886, 3904, 3886, 3960, 3848, 3890, 3871, 4010, 3846, 3962, 3919, 3897, 3884, 3897, 3953, 3901, 3826, 3899, 4008, 3965, 4018, 3999, 3870, 3938, 3853, 3887, 3948, 3889, 3860, 3951, 3920, 3882, 3916, 3913, 3875, 4023, 3944, 3956, 3851, 3921, 3926, 4018, 3916, 3955, 3910, 3842, 3908, 3915, 3935, 3899, 4005, 3975, 3947, 3884, 3869, 3983, 3831, 3931, 3926, 3897, 3927, 3980, 3937, 3953, 3927, 3889, 3849, 3981, 3929, 3929, 3943, 3872, 3993, 3902, 3837, 4080, 3837, 3894, 3810]
Not by any means a scientific test, but I feel good about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see your point about maintainability. I'll take them out and leave in the original tests.
Addresses issue #89.
This was a bit more complex than originally expected. The function is no longer defined onCountableClosedRange<Int>
specifically, but applies to any range withBound
conforming to theSignedInteger
protocol. The arithmetic also had to be altered slightly to account for the generic bound/stride type.Updates after refactor:
Revised replacement for
ip_random(inRange:)
The
ip_random
function now lives on an extension ofCollection
. This means we can still use it to return a random value within a numerical range (since ranges are collections) as well as in many other situations, like say grabbing a random element from an array.The new unit tests remain the same as the first version of this PR: I introduced a way to provide an alternative RNG function instead of
arc4random_uniform
. This was done entirely for testing purposes, and I've left it out of the public API. See the new unit test code and comments for a longer explanation. I'm really interested in feedback as to whether others agree with the testing approach.Replacement of
ip_random()
onUnsignedInteger
There is an additional
ip_random
function that can be called on any class conforming to UnsignedInteger, to return a random value in range [0, max]. A private library [name redacted] uses this to generate random values for UInt8. However the implementation is flawed in that it relies onarc4random_uniform
which expects UInt32. So if the function is called on a larger type, like UInt64 (or UInt on a 64-bit device), the result is an overflow crash.The easiest way to fix this without seeking out a new implementation is to restrict this function for use on UInt32 and under, which I've done by introducing a protocol adopted specifically by UInt8, UInt16, and UInt32.