Skip to content

Instantly share code, notes, and snippets.

@jonhull
Created June 28, 2016 00:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonhull/3655672529f8cf5b2eb248583d2cafb9 to your computer and use it in GitHub Desktop.
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.
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