Swift: Getting EXC_BAD_INSTRUCTION when trying to append random array elements - ios

I am getting the error, "Thread 1: EXC_BAD_INSTRUCTION(code=EXC_1386_INVOP, subcode=0x0)" when trying to append random array elements into a new array.
The debug log says "fatal error: Index out of range"
//If there are more than 6 players prioritizing the event, make a random choice. garudaLocations is an array containing the players who prioritized the event "Garuda".
if garudaLocations.count > 6 {
var finalGarudaPlayers : [Int] = []
let getRandom = randomSequenceGenerator(1, max: garudaLocations.count) //Tell RNG how many numbers it has to pick from.
var randomGarudaPrioritiesIndex = Int()
for _ in 1...6 {
randomGarudaPrioritiesIndex = getRandom() //Generate a random number.
finalGarudaPlayers.append(garudaLocations[randomGarudaPrioritiesIndex]) //ERROR: Thread 1: EXC_BAD_INSTRUCTION(code=EXC_1386_INVOP, subcode=0x0)
}
debugPrint(finalGarudaPlayers) //Print array with the final priority Garuda members.
randomSequenceGenerator is a function I got from here, which does work to generate the random numbers.
func randomSequenceGenerator(min: Int, max: Int) -> () -> Int {
var numbers: [Int] = []
return {
if numbers.count == 0 {
numbers = Array(min ... max)
}
let index = Int(arc4random_uniform(UInt32(numbers.count)))
return numbers.removeAtIndex(index)
}
}
To get a better understanding, I am trying to write a piece of a "team making" program where players are automatically sorted into events, but they can choose which events they would like to prioritize.
I can only have 6 people per event, however, so the goal is to take the existing garudaLocations array, choose a random 6 index locations, and get rid of the rest of the players.
I get the error only after I submit more than 6 players to the same event.
Any help is very much appreciated!

You can never speak of a non-existent index. If you do, you will crash just as you are crashing now.
So, you are saying:
garudaLocations[randomGarudaPrioritiesIndex]
Now, I do not know what garudaLocations is. But I can tell you for certain that if randomGarudaPrioritiesIndex is not an existing index within garudaLocations, you will absolutely crash.
Thus, you can easily debug this by logging (print) randomGarudaPrioritiesIndex.
Bear in mind that the largest existing index is not garudaLocations[garudaLocations.count]. It is garudaLocations[garudaLocations.count-1]. So compare randomGarudaPrioritiesIndex to garudaLocations.count-1. If it is greater, you crash when you use it as an index on garudaLocations.

Related

index(where:) method in swift is producing the wrong index

I have received an array index out of range error. I have two arrays cardsCurrentlyInPlay and currentCardsSelected. Every card that is in this game has a unique ID. I am attempting to find find the index of the card in cardsCurrentlyInPlay whose cardID matches the cardID of the card in currentCardsSelected. I am doing this by using the index(where:) method that takes a closure. My closure just checks if the IDs match, they obviously match because I am using the ! to unwrap them and the app does not crash there. It seems as though the index(where:) method is returning the wrong index. I have looked at this for hours and I do not understand whats going on.
Heres the code:
let indexOfFirstCard = cardsCurrentlyInPlay.index(where: ({($0?.cardID == currentCardsSelected[0].cardID)}))!
let indexOfSecondCard = cardsCurrentlyInPlay.index(where: ({($0?.cardID == currentCardsSelected[1].cardID)}))!
let indexOfThirdCard = cardsCurrentlyInPlay.index(where: ({($0?.cardID == currentCardsSelected[2].cardID)}))!
if deck.isEmpty && selectedCardsMakeASet() {
/* Remove the old cards */
cardsCurrentlyInPlay.remove(at: indexOfFirstCard)
cardsCurrentlyInPlay.remove(at: indexOfSecondCard)
cardsCurrentlyInPlay.remove(at: indexOfThirdCard) // where code is blowing up
currentCardsSelected.removeAll()
/* Return indicies of cards to clear from the UI */
return .deckIsEmpty(indexOfFirstCard, indexOfSecondCard, indexOfThirdCard)
}
The index you’re getting is correct when your get it, but it becomes wrong when you remove other cards. Consider:
var a = ["x", "y", "z"]
let indexOfX = a.index(of: "x")! // returns 0
let indexOfZ = a.index(of: "z")! // returns 2
a.remove(at: indexOfX) // removes "x"; now a = ["y", "z"]
a.remove(at: indexOfZ) // index 2 is now out of bounds
You could interleave the calls to index(of:) and remove(at:), but a better approach would be to remove all three cards in a single pass, something like this:
let selectedCardIDs = currentCardsSelected.map { $0.cardID }
cardsCurrentlyInPlay = cardsCurrentlyInPlay.filter { card in
!selectedCardIDs.contains(card.cardID)
}
Note that this has the added benefit of avoiding the force unwrap, a sign of sounder logic.
This is because of out of bounds.
After the first two code excuted.
cardsCurrentlyInPlay.remove(at: indexOfFirstCard) &
cardsCurrentlyInPlay.remove(at: indexOfSecondCard)
there is only one element in the cardsCurrentlyInPlay .
Then if you excute cardsCurrentlyInPlay.remove(at: indexOfThirdCard), the program will crash.

Iterate through an optional array in Swift?

I'm using the cocoapod SQLite for this project like so
import SQLite
var db = try! Connection()
var id: Expression<Int>!
var identifier: Expression<String>!
With it I am reading a list of moves from a SQLite database.
Every monster has a moves that they can learn. Some monsters can learn more moves than others.
var monster: Monster!
var monArray = [Monster]()
var dataSource: DataSource!
To get the monsters move ID I use this code. This allows me to grab the first move in the array. Changing the 0 would get me the second, third move ect.
monster.moves![0]["move_id"] as! Int
Now I'm using the SQLite database because I need to match monster ID values in my plist with the ones in the SQLite database. I use this code to do so
override func viewWillAppear(_ animated: Bool) {
let movesArray = Array(try! db.prepare(Table("moves").where(identifier == moves.name!.lowercased())))
for user in movesArray {
monArray = dataSource.mons.filter{ $0.moves![0]["move_id"] as! Int == user[id] }
}
}
Everything works fine until I try to increase the index range.
for user in movesArray {
for i in 0...6 {
monArray = dataSource.mons.filter{ $0.moves![i]["move_id"] as! Int == user[id] }
}
}
See where I replace the 0 with the range i? I do that because since monsters have more than one move, if I leave it at 0 my app will only display the monsters that learn that move as their first move. To better explain, my current code does not look through whether the monster knows the move, it only looks through whether the monsters knows the move as its first move.
In the above code I increase the range thinking it would solve my issue, but my app will crash because some monsters only have 1 move in their index, so anything above index 0 will crash with the error
fatal error: Index out of range
So to recap, I need to iterate through the entire array instead of just the first index, without it crashing. How can I achieve this?
Without all the story arround your just asking how to iterate over an array like here
for item in array as type {
...
}
Your question seems to be more of a logical question. If I'm understanding what you have said, then each monster will have a minimum of 1 move, but not guaranteed to have more. So you would need to account for this.
Having a fixed limit like you do will certainly cause problems if not all monsters have that many moves as the array will not always be that size.
I would do something like:
let monsterMoveCount = user.moves.count
for i in 0...monsterMoveCount
// Do whatever logic here
Hopefully this helps!

App Crash on Table Row load

I'm pulling data in from CloudKit, and there is only one item in the data.
Once the matchup loads, and before the background color is seen in the UI, the app crashes. I can't figure out why, any ideas?
InterfaceController on Watch:
func loadTable() {
self.rowTable.setNumberOfRows(self.matchupArray.count, withRowType: "rows")
let rowCount = self.rowTable.numberOfRows
for i in 0...rowCount {
let row = self.rowTable.rowController(at: i) as! Rows!
row?.matchup.setText(self.matchupArray[i])
let colorBackground = UIColor.init(hex: self.teamColorArray[i])
row?.groupColor.setBackgroundColor(colorBackground)
}
}
func getData() {
cloud.getCloudKit { (game: [GameWatch]) in
var teamColorArray = [String]()
var matchupArray = [String]()
for item in game {
teamColorArray.append(item.teamColor)
matchupArray.append(item.matchup)
}
self.teamColorArray = teamColorArray
self.matchupArray = matchupArray
self.loadTable()
}
}
UPDATE:
Got a crash, with the error "fatal error: Index out of range".
I'm not sure why this is, because the matchupArray.count is 1, the rowCount is 1. It started iterating through the for-loop with i as 0, and finished the first iteration where it should have stopped since there was only 1 item. But I got the crash because it started to iterate through the loop again, with i as 1, and then obviously found nothing so it crashed.
The crash comes after row?.matchup.setText(self.matchupArray[i]) is run.
The error is in the line:
for i in 0...rowCount
This ... operator creates a range of indexes that includes both values when, because Swift uses 0-based arrays, you need the ..< operator to create a range that excludes the upper value.
This line should therefore be:
for i in 0..<rowCount

App crashes if playlist count = 0 (Do empty playlists have a persistentid?)

My app is crashing if a playlist is empty (no songs). My app works for all non-empty playlists. It seems like there isn't a persistentid for an empty playlist, but I think I am wrong on that.
let qryPlaylists = MPMediaQuery.playlistsQuery()
var selectedPlaylistTitle: String!
var selectedPlaylistPersistentID: UInt64!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let indexPath: NSIndexPath = playlistsTableView.indexPathForSelectedRow!
let rowItem = qryPlaylists.collections![indexPath.row]
let playlistSize = rowItem.count
print("playlistSize = ", playlistSize)
selectedPlaylistTitle = rowItem.valueForProperty(MPMediaPlaylistPropertyName) as! String
print("selectedPlaylistTitle = ", selectedPlaylistTitle)
// Will crash on this next line if the playlistSize = 0
selectedPlaylistPersistentID = rowItem.items[0].persistentID
// If I use following line instead, it will crash on any playlist
// selectedPlaylistPersistentID = rowItem.valueForProperty(MPMediaPlaylistPropertyPersistentID) as! UInt64
// This line will never be printed
print("selectedPlaylistPersistentID = ", selectedPlaylistPersistentID)
}
Thanks in advance for any help!
If an array such as items is empty, it has no index 0 and will crash if you refer to it, with an out-of-bounds error. So if you don't want to crash, don't do that. You already know that items is empty, because rowItem.count told you so; as you said yourself, playlistSize is 0 in that case.
A simple-minded way to look at it is this: the largest legal index in an array is one less than its count.
Another issue you asked about is that this line always crashes:
selectedPlaylistPersistentID = rowItem.valueForProperty(MPMediaPlaylistPropertyPersistentID) as! UInt64
The problem here is that you are apparently using Swift 2.x. (You should have said that in your question; I deduce it, though, from the fact that valueForProperty has not changed to value(forProperty:), which is what it is called in Swift 3.)
In Swift 2, you cannot cast directly down to UInt64. (I am surprised that the compiler does not draw your attention to this fact with a warning.) Thus, the cast fails and you crash. You need to cast down to NSNumber and then take that NSNumber's unsignedLongLongValue.
And while you are doing this, you really should stop using exclamation marks in your code. When I say "cast down", I mean "cast down safely". My own Swift 2 code for doing this sort of thing looks like this:
if let id = (rowItem.valueForProperty(MPMediaItemPropertyAlbumPersistentID) as? NSNumber)?.unsignedLongLongValue {
// ...
}

Basic Xcode - reference in random number generator not working

I am working on a very basic (I think) program in Xcode. I am trying to write an app for "drawing straws" where the last person who is chosen is the loser. There are a few things I'd like to do. First, see the code below:
import UIKit
let players = 4
var playerNames: [String] = ["John", "Tyler", "Pete", "Dave"]
var draw = Int(arc4random_uniform(4))
playerNames.removeAtIndex(draw)
print(playerNames)
let round2 = playerNames.count
var draw2 = Int(arc4random_uniform(2))
playerNames.removeAtIndex(draw2)
print(playerNames)
let round3 = playerNames.count
var draw3 = Int(arc4random_uniform(1))
playerNames.removeAtIndex(draw3)
print(playerNames)
The first thing that's wrong is I'm currently hard-coding the random integer being drawn in var draw = Int(arc4random_uniform(4)). When I try to reference players instead of just typing in 4, I get an error. Can someone please help explain the problem there?
I'll stop there for now to see if I can fix that, and I'll wait until that is fixed before posting a new question. Thank you.
The function arc4random_uniform() takes a UInt32, so you need to declare the variable players as UInt32
let players: UInt32 = 4
var draw = (arc4random_uniform(players))
For handling the round variables, you would cast the count to UInt32.
let round2 = UInt32(playerNames.count)
You can also refactor your code
let players: UInt32 = 4
var playerNames: [String] = ["John", "Tyler", "Pete", "Dave"]
println(playerNames)
for loop in 1...players {
RemovePlayer(&playerNames)
println(playerNames)
}
And the code for the RemovePlayer function
func RemovePlayer(inout names: [String]) {
// get the number of names in the array
var maxNames = UInt32(names.count)
var draw = Int((arc4random_uniform(maxNames)))
names.removeAtIndex(draw)
}
As Blackfrog already stated your randomization function requires a UInt32
Since you stated that you just started programing I would like to give you some other advice. Your code can be written as:
var playerNames = ["John", "Tyler", "Pete", "Dave"]
for var index = playerNames.count ; index > 1 ; index -= 1{
var random:Int = Int(arc4random_uniform(UInt32(index)))
playerNames.removeAtIndex(random)
}
println(playerNames[0])
This has a few advantages. At the moment you are hardcoding the values for how many players join a game. You will need to re-write an unnecessary amount of code if you want to add a player.
Let’s see what the above code does:
We start by declaring the playerNames adding a type declaration of [String] isn’t necessary, swift already knows that it is this type by its initial value.
for var index = playerNames.count; index > 1 ; index -= 1{
We create a new variable called index this will be set to the amount of items in the playerNames array (4 in this case)
After this we will declare that we want the loop as long as our index is greater than 1 this will make it run 3 times and make sure we are left with 1 item in our array
The index -=1 will subtract 1 from the index after each iteration through the loop.
var random:Int = Int(arc4random_uniform(UInt32(index)))
Here we declare a new variable random this will be of type Integer. Let’s work from the inside to the outside. UIt32(index) will convert our index which is of type Int to a type of UInt32, this is needed cause our random function requires a UInt32
Next up we request a random value which will lay between the index and 0 (thus between the bounds of the array). We want to remove the player who belongs to this random value in our array. To do this we need to convert our random Uint32 back to an Int we can do this by using Int().
Next we remove the player at the index using
playerNames.removeAtIndex(random)
Lastly we print the first (and only) item left in the array using
println(playerNames[0])

Resources