So I've been having a few problems with this shuffling code and posted a few questions on it, and now everything seems to be working, except the actual shuffling doesn't appear to be working.
I have been asked to rewrite this question repeatedly, first with the Swift 3 version of the shuffle code located here as well as now the Swift 4 code. I am on Swift 3.2, and I was getting warnings with the Swift 3 code. I can include them if desired, however I've added and deleted them several times already because a user keeps asking me to change my question
I've tried this:
func displayOfferCards() -> Void {
var offerCards = allOfferCards()
offerCards.shuffle()
for (index, offerCard) in offerCards.enumerated() {
let delay = Double(index) * 0.2
offerCard.display(delay: delay)
}
}
Here's also this for how the cards are being generated:
func allOfferCards() -> [OfferCard]{
guard dataSource != nil else {
return []
}
let numberOfCards = self.dataSource!.kolodaNumberOfCards(self)
var offerCards = [OfferCard]()
for i in 0..<numberOfCards {
let offerCard = viewForCard(at: i)
if let offerCard = offerCard {
offerCards.append(offerCard as! OfferCard)
}
}
return offerCards
}
And here's the shuffle function:
extension MutableCollection {
/// Shuffles the contents of this collection.
mutating func shuffle() {
let c = count
guard c > 1 else { return }
for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
let d: IndexDistance = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
let i = index(firstUnshuffled, offsetBy: d)
swapAt(firstUnshuffled, i)
}
}
}
An example, before and after:
print("DEBUG TOP " + offerCards[0].offer.title)
offerCards.shuffle()
print("DEBUG BOTTOM " + offerCards[0].offer.title)
Using the method removeFirst() is working:
offerCards.removeFirst()
So it is definitely something to do with the shuffle function itself I think.
And with PhilipMillis' suggestion:
DEBUG TOP Apple Music
DEBUG Swapping 0 with 0
DEBUG Swapping 1 with 2
DEBUG BOTTOM Apple Music
DEBUG TOP Apple Music
DEBUG Swapping 0 with 1
DEBUG Swapping 1 with 2
DEBUG BOTTOM Nielsen Rewards
Related
Is it possible to support moving rows in a SwiftUI Table view?
I know there's List which can optionally support selection and drag-and-drop to move single or multiple rows. Since it seems similar, I would like to do this with a Table too, but I wasn't able to find any way to do this. Is this possible in SwiftUI? And if it is, what's the best way to do it?
Where I started to figure this out was the WWDC 2021 session "SwiftUI on the Mac: Finishing Touches". I highly recommend this video, as well as the preceding one "SwiftUI on the Mac: Build the Fundamentals". The code for both sessions is available.
Since you didn't include your code to show what you want to do, I have to use my code. I have a table based on an array of an Identifiable struct called Channel. Among a number of fields which are irrelevant to this problem, there is a field "id" of type UUID.
Following the model of the WWDC video, I made an extension to Channel:
import UniformTypeIdentifiers
extension Channel {
static var draggableType = UTType(exportedAs: "com.yourCompany.yourApp.channel")
// define your own type here. don't forget to include it in your info.plist as an exported type
static func fromItemProviders(_ itemProviders: [NSItemProvider], completion: #escaping ([Channel]) -> Void) {
let typeIdentifier = Self.draggableType.identifier
let filteredProviders = itemProviders.filter {
$0.hasItemConformingToTypeIdentifier(typeIdentifier)
}
let group = DispatchGroup()
var result = [Int: Channel]()
for (index, provider) in filteredProviders.enumerated() {
group.enter()
provider.loadDataRepresentation(forTypeIdentifier: typeIdentifier) { (data, error) in
defer { group.leave() }
guard let data = data else { return }
let decoder = JSONDecoder()
guard let channel = try? decoder.decode(Channel.self, from: data)
else { return }
result[index] = channel
}
}
group.notify(queue: .global(qos: .userInitiated)) {
let channels = result.keys.sorted().compactMap { result[$0] }
DispatchQueue.main.async {
completion(channels)
}
}
}
var itemProvider: NSItemProvider {
let provider = NSItemProvider()
provider.registerDataRepresentation(forTypeIdentifier: Self.draggableType.identifier, visibility: .all) {
let encoder = JSONEncoder()
do {
let data = try encoder.encode(self)
$0(data, nil)
} catch {
$0(nil, error)
}
return nil
}
return provider
}
}
This makes an item in the table draggable. Of course, that does no good if there's nothing that will accept the drag. So, you have to make a change to your Table.
Table(selection: $selection, sortOrder: $sortOrder) {
// for clarity, I've removed the table columns
} rows: {
ForEach(document.channels) { channel in
TableRow(channel)
.itemProvider { channel.itemProvider }
}
.onInsert(of: [Channel.draggableType]) { index, providers in
Channel.fromItemProviders(providers) { channels in
document.channels.insert(contentsOf: channels, at: newIndex)
}
}
}
}
Now that will enable you to drag item or items from one window to another. You can, of course, drag within a table now, too. Unfortunately, you will end up making a copy in the new place. Not what you want to do in most cases. How to fix this? Delete the original copy! Of course, you can also run into the problem of indexing in the right place, and if you drag more than one item (from a discontinuous selection, even worse!), the results become, shall we say, undefined.
I still wanted to be able to drag multiple items from another table, so the final onInsert becomes a little more complex (Which I'm sure could be cleaned up a bot further):
Channel.fromItemProviders(providers) { channels in
var newIndex = index
let intraTableDrag = document.channels.contains(where: {$0.id == channels[0].id})
if intraTableDrag && channels.count == 1 {
let oldIndex = document.channels.firstIndex(where: {$0.id == channels[0].id})
if newIndex > oldIndex! {
newIndex -= 1
}
for channel in channels {
let channelID = channel.id
removeChannel(withID: channelID)
}
let maxIndex = document.channels.count
if index > maxIndex {
newIndex = maxIndex
}
}
if (intraTableDrag && channels.count == 1) || !intraTableDrag {
document.channels.insert(contentsOf: channels, at: newIndex)
document.setChannelLocationToArrayOrder()
}
}
}
I hope this is enough to get you started. Good luck!
This is a multiple audiofile playback project I am currently working on, where multiple AKPlayers are played in a random order, through AKSequencer.
First, I have an array of filenames:
Let filenames = [“1.mp3”, “2.mp3, “3.mp3”, … “10.mp3”]
This is loaded on AKPlayer individually, to later call it in a random order:
Let players: [AKPlayer] = {
Do {
Let filenames = [“1.mp3”, “2.mp3, “3.mp3”, … “10.mp3”]
Return try filenames.map { AKPlayer(audioFile: try AKAudioFile(readRileName: $0)) }
} catch {
fatalError()
}
}()
Then, I called AKPlayer through AKsequencer, by triggering it through ‘playRandom’ function:
Let sequencer = AKSequencer()
Let callbackInst = AKCallbackInstrument()
func playRandom() {
let playerIndex = Int(arc4random_uniform(UInt32(players.count)))
players[playerIndex].play()
}
func addTracks() {
let track = sequencer.newTrack()!
track.add(noteNumber: 48, velocity: 127, position: AKDuration(beats: 0), duration: AKDuration(beats: 16), channel: 0)
track.setMIDIOutput(callbackInst.midiIn)
callbackInst.callback = { status, note, vel in
guard status == .noteOn else { return }
self.playRandom()
}
}
Lastly, I set AKPlayer as AudioKit.output, and started the sequencer.
So far this was successful! The sequencer plays AKPlayer seamlessly, in a random order.
But I wanted to try different kind of randomness: repeating randomly selected player 2 or 3 times. (like 2, 2, 3, 3, 3, 1, 1, 5, 5, 5, 9, 9, …) Right now, ‘playRandom’ simply chooses different AKPlayer on each repeat.
As one solution, thanks to StackOverFlow masters, I tried something like:
class RandomWithRepeats {
var range: ClosedRange<Int>
var repeatRange: ClosedRange<Int>
var repeatCount = 0
var value = 0
init(range: ClosedRange<Int>, repeatRange: ClosedRange<Int>) {
self.range = range
self.repeatRange = repeatRange
}
// generate a random number in a range
// Just use Int.random(in:) with Swift 4.2 and later
func random(in range: ClosedRange<Int>) -> Int {
return Int(arc4random_uniform(UInt32(range.upperBound - range.lowerBound + 1))) + range.lowerBound
}
func nextValue() -> Int {
// if repeatCount is 0, its time to generate a new value and
// a new repeatCount
if repeatCount == 0 {
// For Swift 4.2, just use Int.random(in:) instead
value = self.random(in: range)
repeatCount = self.random(in: repeatRange)
}
repeatCount -= 1
return value
}
}
And then I modified playRandom function like:
func playRandom() {
Let rand = randomWithRepeats(range: 1…players.count, repeatRange: 2…3)
Do { players[rand.nextValue()].play() }
}
Turns out, this is the exact same result because the playRandom function itself triggered (by AKSequencer) each repeat, so it doesn’t actually ‘repeat’ random AKPlayer 2 or 3 times. Can I solve this issue in a different way? Much appreciated. <3
The job of the RandomWithRepeats class is to keep track of when to deliver you a different AKPlayer instance, but you are creating a new instance of RandomWithRepeats each time you call playRandom(), so it can't keep track of anything.
Create a single instance of RandomWithRepeats when you create the array of players and use this instance in your playRandom() method.
let rand = RandomWithRepeats(range: 0 ... players.count, repeatRange: 2 ... 3)
func playRandom() {
try? players[rand.nextValue()].play()
}
For a cleaner solution, you might encapsulate the logic of RandomWithRepeats together with the AKPlayer array into a single class, such as a RandomlyRepeatingPlayer class.
i have some question about swift 2 random. I have an enum sub class of all cards example:
enum CardName : Int{
case Card2Heart = 0,
Card2Diamond,
Card2Club,
Card2Spade,
Card3Heart..... }
I want to select 10 random cards on the didMoveToView
To get a unique, random set of numbers you can do the following...
Using the Fisher-Yates shuffle from here... How do I shuffle an array in Swift?
You can do...
var numbers = Array(0...51)
numbers.shuffleInPlace()
let uniqueSelection = numbers[0..<10]
or...
let uniqueSelection = Array(0...51).shuffleInPlace()[0..<10]
This will create a random, unique selection of 10 numbers (cards) from the array of 52 cards that you start with.
You can then iterate this array to get the enums or create an array of all enums to start from etc... There are lots of ways to use this.
In Swift 4.2 (coming with Xcode 10) the task will become much easier:
enum CardName: CaseIterable {
case Card2Heart
case Card2Diamond
case Card2Club
case Card2Spade
case Card3Heart
// ...
}
let randomCard = CardName.allCases.randomElement()
print(randomCard)
let randomCards10 = CardName.allCases.shuffled().prefix(10)
print(randomCards10)
Note there is no need for the enum to inherit from Int.
Following your last comment, here's a little, simplified example with the constraint of having to keep your enum for making the cards.
We need to include the extensions linked by Fogmeister:
extension MutableCollectionType where Index == Int {
/// Shuffle the elements of `self` in-place.
mutating func shuffleInPlace() {
// empty and single-element collections don't shuffle
if count < 2 { return }
for i in 0..<count - 1 {
let j = Int(arc4random_uniform(UInt32(count - i))) + i
guard i != j else { continue }
swap(&self[i], &self[j])
}
}
}
extension CollectionType {
/// Return a copy of `self` with its elements shuffled
func shuffle() -> [Generator.Element] {
var list = Array(self)
list.shuffleInPlace()
return list
}
}
These extensions will allow us to shuffle an array of values.
Which array?
There's many ways, but the simplest option is probably to make an array of indices, which are simple integers (replace 52 with the actual number of cards in your enum):
Array(1...52) // [1, 2, 3, ... , 52]
We shuffle it:
Array(1...52).shuffle() // [33, 42, 7, ...]
Now we have an array of randomized indices. Let's make cards from this with your enum:
Array(0...51).shuffle().flatMap({ CardName(rawValue: $0) })
This is it, we have an array of cards in a random order:
let shuffledDeck = Array(0...51).shuffle().flatMap({ CardName(rawValue: $0) }) // [Card3Heart, Card2Diamond, ...]
and we can take cards from it:
func takeCardsFromDeck(number: Int) -> [CardName] {
if shuffledDeck.count > number {
let cards = Array(shuffledDeck[0..<number])
shuffledDeck.removeRange(0..<number)
return cards
}
return []
}
let tenRandomCards = takeCards(10)
Of course we need to remove from the deck the cards we've dealt, that way each card you draw is unique: we're using removeRange for that.
This example was kept simple on purpose: you still have to verify that there's enough cards in the deck before drawing, and lots of unsuspected other complexities. But it's so fun. ;)
If you want, you can search for additional inspiration in my implementation of these models and others (Deck, Dealer, Player, etc) in my PokerHands repository (MIT Licenced) on GitHub.
Swift 4.2
No need for these extensions anymore, we can use the .shuffle() and .shuffled() methods provided by Swift. Just remove the extensions, and rename the methods: the equivalent of our old "shuffleInPlace" is now .shuffle() and the equivalent of our old "shuffle" is now .shuffled().
Note: see Sulthan's answer for an even better solution using Swift 4.2.
Here is the shuffleInPlace() code that you are missing;
extension MutableCollectionType where Index == Int {
mutating func shuffleInPlace() {
if count < 2 { return }
for i in 0..<count - 1 {
let j = Int(arc4random_uniform(UInt32(count - i))) + i
guard i != j else { continue }
swap(&self[i], &self[j])
}
}
}
how to randomly spread enum values set
import Darwin // arc4random_uniform
enum E:Int {
case E1, E2, E3, E4, E5, E6, E7, E8, E9, E10
static var set:[E] { return (E.E1.rawValue...E.E10.rawValue).flatMap { E(rawValue: $0) }}
}
func spread(i:Int = 0, arr:[E])->([E],[E]) {
var i = i == 0 ? arr.count : i
var e:[E] = []
var arr = arr
while i > 0 && arr.count > 0 {
let idx = Int(arc4random_uniform(UInt32(arr.count-1)))
e.append(arr.removeAtIndex(idx))
i -= 1
}
return (e,arr)
}
let e1 = spread(3, arr: E.set)
let e2 = spread(2, arr: e1.1)
// ... spread the rest
let e3 = spread(arr: e2.1)
print(e1, e2, e3, separator:"\n")
/*
([E.E8, E.E6, E.E4], [E.E1, E.E2, E.E3, E.E5, E.E7, E.E9, E.E10])
([E.E1, E.E7], [E.E2, E.E3, E.E5, E.E9, E.E10])
([E.E5, E.E3, E.E2, E.E9, E.E10], [])
*/
I'm needing to create a random bool value in my game, in Swift.
It's basically, if Yes (or 1), spawn one object, if No (or 0), spawn the other.
So far, looking at this question and a similar one on here, I found this:
let randomSequenceNumber = Int(arc4random_uniform(2))
Now it works, but it seems bias to 0 to me... like ridiculously bias...
This is how I'm then using the value:
if(randomSequenceNumber == 0)
//spawn object A
else
//spawn object B
Is there a better way to implement this using a random bool value? That isn't bias to a certain value?
Update
Bit of an experiment to see how many 1's vs 0's were were generated in 10,000 calls:
func experiment() {
var numbers: [Int] = []
var tester: Int = 0
var sum = 0
for i in 0...10000 {
tester = Int(arc4random_uniform(2))
numbers.append(tester)
print(i)
}
for number in numbers {
sum += number
}
print("Total 1's: ", sum)
}
Test 1: Console Output: Total 1's: 4936
Test 2: Console Output: Total 1's: 4994
Test 3: Console Output: Total 1's: 4995
Xcode 10 with Swift 4.2
Looks like Apple engineers are listening
let randomBool = Bool.random()
import Foundation
func randomBool() -> Bool {
return arc4random_uniform(2) == 0
}
for i in 0...10 {
print(randomBool())
}
for more advanced generator the theory is available here
for basic understanding of Bernoulli (or binomial) distribution check here
extension Bool {
static func random() -> Bool {
return arc4random_uniform(2) == 0
}
}
// usage:
Bool.random()
I have a large array that I would like to process by handing slices of it to a few asynchronous tasks. As a proof of concept, I have the written the following code:
class TestParallelArrayProcessing {
let array: [Int]
var summary: [Int]
init() {
array = Array<Int>(count: 500000, repeatedValue: 0)
for i in 0 ..< 500000 {
array[i] = Int(arc4random_uniform(10))
}
summary = Array<Int>(count: 10, repeatedValue: 0)
}
func calcSummary() {
let group = dispatch_group_create()
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
for i in 0 ..< 10 {
dispatch_group_async(group, queue, {
let base = i * 50000
for x in base ..< base + 50000 {
self.summary[i] += self.array[x]
}
})
}
dispatch_group_notify(group, queue, {
println(self.summary)
})
}
}
After init(), array will be initialized with random integers between 0 and 9.
The calcSummary function dispatches 10 tasks that take disjoint chunks of 50000 items from array and add them up, using their respective slot in summary as an accummulator.
This program crashes at the self.summary[i] += self.array[x] line. The error is:
EXC_BAD_INSTRUCTION (code = EXC_I386_INVOP).
I can see, in the debugger, that it has managed to iterate a few times before crashing, and that the variables, at the time of the crash, have values within correct bounds.
I have read that EXC_I386_INVOP can happen when trying to access an object that has already been released. I wonder if this has anything to do with Swift making a copy of the array if it is modified, and, if so, how to avoid it.
This is a slightly different take on the approach in #Eduardo's answer, using the Array type's withUnsafeMutableBufferPointer<R>(body: (inout UnsafeMutableBufferPointer<T>) -> R) -> R method. That method's documentation states:
Call body(p), where p is a pointer to the Array's mutable contiguous storage. If no such storage exists, it is first created.
Often, the optimizer can eliminate bounds- and uniqueness-checks within an array algorithm, but when that fails, invoking the same algorithm on body's argument lets you trade safety for speed.
That second paragraph seems to be exactly what's happening here, so using this method might be more "idiomatic" in Swift, whatever that means:
func calcSummary() {
let group = dispatch_group_create()
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
self.summary.withUnsafeMutableBufferPointer {
summaryMem -> Void in
for i in 0 ..< 10 {
dispatch_group_async(group, queue, {
let base = i * 50000
for x in base ..< base + 50000 {
summaryMem[i] += self.array[x]
}
})
}
}
dispatch_group_notify(group, queue, {
println(self.summary)
})
}
When you use the += operator, the LHS is an inout parameter -- I think you're getting race conditions when, as you mention in your update, Swift moves around the array for optimization. I was able to get it to work by summing the chunk in a local variable, then simply assigning to the right index in summary:
func calcSummary() {
let group = dispatch_group_create()
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
for i in 0 ..< 10 {
dispatch_group_async(group, queue, {
let base = i * 50000
var sum = 0
for x in base ..< base + 50000 {
sum += self.array[x]
}
self.summary[i] = sum
})
}
dispatch_group_notify(group, queue, {
println(self.summary)
})
}
You can also use concurrentPerform(iterations: Int, execute work: (Int) -> Swift.Void) (since Swift 3).
It has a much simpler syntax and will wait for all threads to finalise before returning.:
DispatchQueue.concurrentPerform(iterations: iterations) { i in
performOperation(i)
}
I think Nate is right: there are race conditions with the summary variable. To fix it, I used summary's memory directly:
func calcSummary() {
let group = dispatch_group_create()
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
let summaryMem = UnsafeMutableBufferPointer<Int>(start: &summary, count: 10)
for i in 0 ..< 10 {
dispatch_group_async(group, queue, {
let base = i * 50000
for x in base ..< base + 50000 {
summaryMem[i] += self.array[x]
}
})
}
dispatch_group_notify(group, queue, {
println(self.summary)
})
}
This works (so far).
EDIT
Mike S has a very good point, in his comment below. I have also found this blog post, which sheds some light on the problem.
Any solution that assigns the i'th element of the array concurrently risks race condition (Swift's array is not thread-safe). On the other hand, dispatching to the same queue (in this case main) before updating solves the problem but results in a slower performance overall. The only reason I see for taking either of these two approaches is if the array (summary) cannot wait for all concurrent operations to finish.
Otherwise, perform the concurrent operations on a local copy and assign it to summary upon completion. No race condition, no performance hit:
Swift 4
func calcSummary(of array: [Int]) -> [Int] {
var summary = Array<Int>.init(repeating: 0, count: array.count)
let iterations = 10 // number of parallel operations
DispatchQueue.concurrentPerform(iterations: iterations) { index in
let start = index * array.count / iterations
let end = (index + 1) * array.count / iterations
for i in start..<end {
// Do stuff to get the i'th element
summary[i] = Int.random(in: 0..<array.count)
}
}
return summary
}
I've answered a similar question here for simply initializing an array after computing on another array