-
-
Save jonhull/3655672529f8cf5b2eb248583d2cafb9 to your computer and use it in GitHub Desktop.
RandomSource is a Swift 3 collection which allows creation of a random sequence of any type which conforms to its RandomSourceCreatable protocol.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public protocol RandomSourceCreatable:Equatable { | |
init(rnd value:RandomSourceValue, lowerBound:Self?, upperBound:Self?) | |
static var randomSourceDimension:RandomSourceDimension {get} | |
} | |
public struct RandomSource<T:RandomSourceCreatable>:Collection { | |
public typealias Index = Int | |
public var seed:UInt32 | |
public var count: RandomSource.IndexDistance | |
public var lowerBound:T? | |
public var upperBound:T? | |
public init(seed:UInt32 = arc4random_uniform(UInt32.max), count:Int? = nil, from:T? = nil, to:T? = nil){ | |
let dim = T.randomSourceDimension | |
dim.failIfInvalid() | |
let maxCount:Int = Int(UInt32.max) / dim.wordCount | |
self.seed = seed | |
self.count = Swift.min(count ?? maxCount, maxCount) | |
self.lowerBound = from | |
self.upperBound = to | |
} | |
public var startIndex : RandomSource.Index { return 0 } | |
public var endIndex : RandomSource.Index { return self.count} | |
public subscript(position: RandomSource.Index) -> T { | |
return T(rnd: randomForIndex(idx: position), lowerBound: self.lowerBound, upperBound: self.upperBound) | |
} | |
public func index(after i: RandomSource.Index) -> RandomSource.Index{ | |
return i + 1 | |
} | |
private func rotateLeft(_ num:UInt32, by len:UInt32)->UInt32{ | |
return (num << len) | (num >> (32-len)) | |
} | |
private func mix(_ num:UInt32)->UInt32 { | |
var num = num | |
num ^= num >> 16 | |
num = num &* 0x85ebca6b | |
num ^= num >> 13 | |
num = num &* 0xc2b2ae35 | |
num ^= num >> 16 | |
return num | |
} | |
private func murmur(index:Int) -> UInt32 { | |
let c1:UInt32 = 0xcc9e2d51 | |
let c2:UInt32 = 0x1b873593 | |
var h = self.seed | |
var k = UInt32(index) | |
k = k &* c1 | |
k = rotateLeft(k, by: 15) | |
k = k &* c2 | |
h ^= k | |
h = rotateLeft(h, by: 13) | |
h = h &* 5 &+ 0xe6546b64 | |
h ^= UInt32(4) | |
h = mix(h) | |
return h | |
} | |
private func randomForIndex(idx:Int)->RandomSourceValue { | |
let dim = T.randomSourceDimension | |
switch dim { | |
case .single: return RandomSourceValue(murmur(index: idx)) | |
case .single64: return RandomSourceValue(pairFor64: murmur(index: 2 * idx), murmur(index: 2*idx + 1)) | |
case .many(let size): | |
let array = (0..<size).map({ murmur(index: size * idx + $0)}) | |
return RandomSourceValue(array) | |
case .many64(let size): | |
let twiceSize = size * 2 | |
let array = (0..<twiceSize).map({ murmur(index: twiceSize * idx + $0)}) | |
return RandomSourceValue(array, as64:true) | |
} | |
} | |
} | |
public enum RandomSourceDimension { | |
case single | |
case single64 | |
case many(Int) | |
case many64(Int) | |
var uses64:Bool { | |
switch self { | |
case .single64: return true | |
case .many64(_): return true | |
default: return false | |
} | |
} | |
var wordCount:Int { | |
switch self { | |
case .single: return 1 | |
case .single64: return 2 | |
case .many(let cnt): return cnt | |
case .many64(let cnt): return cnt * 2 | |
} | |
} | |
func failIfInvalid() { | |
switch self { | |
case .single: return | |
case .single64: return | |
case .many(let cnt): | |
if cnt <= 0 {fatalError("Attempt to make Random Source with 0 dimension")} | |
case .many64(let cnt): | |
if cnt <= 0 {fatalError("Attempt to make Random Source with 0 dimension")} | |
} | |
} | |
} | |
public struct RandomSourceValue { | |
private var _store:Store | |
private enum Store { | |
case single(UInt32) | |
case single64(UInt64) | |
case array([UInt32]) | |
case array64([UInt64]) | |
var uses64:Bool { | |
switch self { | |
case .single64: return true | |
case .array64(_): return true | |
default: return false | |
} | |
} | |
var count:Int { | |
switch self { | |
case .single(_): return 1 | |
case .single64(_): return 1 | |
case .array(let array): return array.count | |
case .array64(let array): return array.count | |
} | |
} | |
func value32(at idx:Int, allow64:Bool = false)->UInt32 { | |
guard idx < self.count else { | |
fatalError("Attempt to get index \(idx) in value of dimension \(self.count)") | |
} | |
switch self { | |
case .single(let i): return i | |
case .array(let a): return a[idx] | |
case .single64(let i64) where allow64 == true: return UInt32(truncatingBitPattern: i64) | |
case .array64(let a64) where allow64 == true: return UInt32(truncatingBitPattern: a64[idx]) | |
default: fatalError("Attempt to get 32bit value from 64 bit random source") | |
} | |
} | |
func value64(at idx:Int, allow32:Bool = false)->UInt64 { | |
guard idx < self.count else { | |
fatalError("Attempt to get index \(idx) in RandomSourceValue of dimension \(self.count)") | |
} | |
switch self { | |
case .single64(let i64): return i64 | |
case .array64(let a64): return a64[idx] | |
case .single(let i) where allow32 == true: return UInt64(i) | |
case .array(let a) where allow32 == true: return UInt64(a[idx]) | |
default: fatalError("Attempt to get 64bit value from 32 bit random source") | |
} | |
} | |
} | |
init(_ single:UInt32) { | |
self._store = .single(single) | |
} | |
init(_ single:UInt64) { | |
self._store = .single64(single) | |
} | |
init(pairFor64 a:UInt32, _ b:UInt32){ | |
self._store = .single64(UInt64(a)<<32 + UInt64(b)) | |
} | |
init(_ array:[UInt32], as64:Bool = false) { | |
let cnt = array.count | |
guard cnt > 0 else { | |
fatalError("Attempt to create random value with empty array") | |
} | |
if as64{ | |
guard cnt % 2 == 0 else { | |
fatalError("Attempt to create 64 bit random value with array with odd number of 32bit values") | |
} | |
var array64:[UInt64] = [] | |
for i in 0..<(cnt/2){ | |
let u64 = UInt64(array[2*i]) << 32 + UInt64(array[(2*i)+1]) | |
array64.append(u64) | |
} | |
self._store = .array64(array64) | |
}else { | |
self._store = .array(array) | |
} | |
} | |
init(_ array:[UInt64]) { | |
guard array.count > 0 else { | |
fatalError("Attempt to create random value with empty array") | |
} | |
self._store = .array64(array) | |
} | |
func value(at idx:Int) -> RandomSourceValue { | |
if self._store.uses64 { | |
let val64 = self._store.value64(at: idx) | |
return RandomSourceValue(val64) | |
}else{ | |
let val32 = self._store.value32(at: idx) | |
return RandomSourceValue(val32) | |
} | |
} | |
func valueAsUInt32(at idx:Int = 0, allow64:Bool = false) -> UInt32 { | |
return self._store.value32(at: idx, allow64: allow64) | |
} | |
func valueAsUInt64(at idx:Int = 0, allow32:Bool = false) -> UInt64 { | |
return self._store.value64(at: idx, allow32: allow32) | |
} | |
func valueAsInt32(at idx:Int = 0, allow64:Bool = false) -> Int32 { | |
let u32 = self._store.value32(at: idx, allow64: allow64) | |
return Int32(bitPattern: u32) | |
} | |
func valueAsInt64(at idx:Int = 0, allow32:Bool = false) -> Int64 { | |
let u64 = self._store.value64(at: idx, allow32: allow32) | |
return Int64(bitPattern: u64) | |
} | |
func valueAsPercent(at idx:Int = 0) -> Double {//Return in 0 to 1.0 range | |
if self._store.uses64 { | |
let u64 = self._store.value64(at: idx) | |
return Double(u64)/Double(UInt64.max) | |
}else{ | |
let u32 = self._store.value32(at: idx) | |
return Double(u32)/Double(UInt32.max) | |
} | |
} | |
func valueAsFloat(at idx:Int = 0, allow64:Bool = false) -> Float { | |
let u32 = self._store.value32(at: idx, allow64: allow64) | |
return Float(bitPattern: u32) | |
} | |
func valueAsDouble(at idx:Int = 0) -> Double { | |
let u64 = self._store.value64(at: idx) | |
return Double(bitPattern: u64) | |
} | |
var uses64:Bool { | |
return self._store.uses64 | |
} | |
} | |
extension RandomSource:Hashable { | |
public var hashValue: Int { | |
return Int(seed) | |
} | |
} | |
public func ==<T:RandomSourceCreatable>(lhs:RandomSource<T>, rhs:RandomSource<T>) -> Bool { | |
return lhs.seed == rhs.seed && | |
lhs.count == rhs.count && | |
lhs.lowerBound == rhs.lowerBound && | |
lhs.upperBound == rhs.upperBound | |
} | |
extension RandomSourceCreatable { | |
public static var randomSourceDimension:RandomSourceDimension{ | |
return RandomSourceDimension.single | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment