Index out of range error, array of custom objects. Swift - arrays

I have index out of range error when i delete object from my array. There is my code. It is an Elevator function which takes object of Floor class and works with array of objects of Passenger class on this floor. I create a temporary copy of object of current floor and then I go along array of this copy and if object is suitable for the conditions we push this object to Elevator array of passengers and delete it by index from original array of current floor object. If this matters, I use the Equatable protocol and created a function to compare.
Thanks for any answers.
class Passenger: Equatable{...}
func ==(l: Passenger, r: Passenger) -> Bool {
return l === r
}
func checkFloor(f: Floor){
var tempFloor = f
var pass = passengers
for i in 0..<passengers.count {
if(passengers.isEmpty){
break
}
if(pass[i].getFloor()==f.getIdFloor()){
f.peoples.append(pass[i])
f.peoples[f.peoples.count - 1].setDirection(who: "nothing")
//if var index = passengers.index(of: pass[i]) {
if let index = searchInArray(who: passengers, who: pass[i]) {
passengers.remove(at: index)
}
}
}
// in this part I have a problem
for i in 0..<tempFloor.countOf() {
if(f.peoples.isEmpty || passengers.count >= capacity){
break
}
if(tempFloor.peoples[i].getDirection()==tempFloor.peoplesDirection()
){
passengers.append(tempFloor.peoples[i])
if let index = f.peoples.index(of: tempFloor.peoples[i]) {
if (index >= 0 && index < f.peoples.count) {
//print(index)
f.peoples.remove(at: index) // index out of range error
}
}
}
}
}

You are removing items whilst enumerating a range, so the range is changing (potentially often) but this wont update for i in 0..<tempFloor.countOf()
When you remove an item from an array, each item after that index changes its index and the count is reduced. so if you plan to do this, it's usually best to enumerate the array backwards, so the removal of the current item will not affect what your doing next.
To demonstrate, try this code in a playground
var arr = [1,2,3,4,5,6,7,8,9,10]
for (index, item) in arr.enumerated().reversed() {
if item % 2 == 0 {
arr.remove(at: index)
}
}
print(arr)
It will iterate over the items in the array backwards and remove any that are even, and will output:
"[1, 3, 5, 7, 9]\n"

Related

Shuffle struct by Int

How I can shuffle my struct by variable priority and display in TableView?
So now I have 20 documents in my struct, but later I will have 100+ documents in my struct.
5 or 7 or 10 documents will have priority from 10 to 1, other documents have priority 0. Me need display 5 or 7 or 10 documents on top position in tableView. Other documents which have priority 0 must be located after firsts 5 or 7 or 10 documents in random order.
I. e. the firsts 5 or 7 or 10 documents should be placed depending on the priority, if a document has priority 10, it should be the first one, the next one which has priority 9 should be behind the document with priority 10 and so on to the document with priority 1. Other documents due be randomly in order.
This code which help me get documents from firestore:
fileprivate func observeQuery() {
MBProgressHUD.showAdded(to: self.view, animated: true)
guard let query = query else { return }
let time = DispatchTime.now() + 0.5
listener = query.addSnapshotListener { [unowned self] (snapshot, error) in
if let snapshot = snapshot {
DispatchQueue.main.asyncAfter(deadline: time) {
var photoModels = snapshot.documents.map { (document) -> Photographer in
if let photoModel = Photographer(dictionary: document.data(), id: document.documentID) {
return photoModel
} else {
fatalError("Fatal error")
}
}
self.photographers = photoModels
// this need use shuffle
self.document = snapshot.documents
self.tableView.reloadData()
MBProgressHUD.hide(for: self.view, animated: true)
}
}
}
}
What you could do is
Sort the documents by decreasing priority first.
Then shuffle the part of the array with documents with zero priority.
Example:
var sorted = documents.sorted(by: { $0.priority > $1.priority } )
if let idx = sorted.firstIndex(where: { $0.priority == 0 }) {
sorted[idx...].shuffle()
}
An alternative is to shuffle the complete array and then do a “stable sort” by decreasing priority, using the ideas from How to stable sort an array in swift?:
let sorted = documents.shuffled().enumerated()
.sorted(by: { ($0.element.priority, $0.offset) > ($1.element.priority, $1.offset) })
.map { $0.element }
This would sort the entries by decreasing order, and randomly shuffle all entries with identical priority, not only the entries with zero priority.
Remark: The sort methods from the Swift standard library happens to be stable in Swift 5, so that the last approach could be simplified to
let sorted = documents.shuffled()
.sorted(by: { $0.priority > $1.priority } )
However, that is not guaranteed, compare Is sort() stable in Swift 5? in the Swift forum.
Here is a solution where all elements in the array gets sorted at once
array.sort(by: {
let first = $0.priority == 0 ? Double.random(in: 0..<1) : Double($0.priority)
let second = $1.priority == 0 ? Double.random(in: 0..<1) : Double($1.priority)
return first > second
})
If you want to avoid the cast to Double you can define a negative range for the random numbers, I just hardcoded a value here but one option could be to base the min value (-1000) on the size of array
array.sort(by: {
let first = $0.priority == 0 ? Int.random(in: -1000..<1) : $0.priority
let second = $1.priority == 0 ? Int.random(in: -1000..<1) : $1.priority
return first > second
})
You could put the documents into buckets:
var buckets = Array(repeating: [Document](), count: 11)
for i in documents.indices {
buckets[10 - documents[i].priority].append(documents[i])
}
This is an O(n) algorithm worst case unlike TimSort's O(nlog(n)).
and then shuffle the last bucket :
buckets[10].shuffle()
last but not least, flatMap the buckets array:
let sorted = buckets.flatMap { $0 }
Speed Shuffle
If you'd like to an even faster shuffle, you could use this modified Fisher-Yates Algorithm (Faster than Array.shuffled()):
extension Array where Element: Comparable {
mutating func fisherYatesShuffle() {
if count < 2 {
return
}
for i in 1..<count {
let ix = abs(x.next()) % (i+1)
swapAt(i,ix)
}
}
}
Or more generally for MutableCollection (which would include ArraySlice):
extension MutableCollection where Index == Int, Element: Comparable {
mutating func fisherYatesShuffle() {
if count < 2 {
return
}
let start = self.index(after: startIndex)
for i in start..<endIndex {
let ix = abs(x.next()) % (i + 1 - startIndex) + startIndex
swapAt(i, ix)
}
}
}
This extension uses a Xoshiro random number generator (Faster than Int.random(in:), but less random/uniform):
struct Xoshiro: RandomNumberGenerator {
public typealias StateType = (UInt32, UInt32, UInt32, UInt32)
private var state: StateType
public init(seed: StateType) {
self.state = seed
}
public mutating func next() -> Int {
let x = state.1 &* 5
let result = ((x &<< 7) | (x &>> 25)) &* 9
let t = state.1 &<< 9
state.2 ^= state.0
state.3 ^= state.1
state.1 ^= state.2
state.0 ^= state.3
state.2 ^= t
state.3 = (state.3 &<< 21) | (state.3 &>> 11)
return Int(result)
}
}
var x = Xoshiro(seed: (UInt32.random(in: 0..<10), //Other upper limits could be used to increase randomness
UInt32.random(in: 0..<10),
UInt32.random(in: 0..<10),
UInt32.random(in: 0..<10)))
To use it, Document should be Comparable :
struct Document: Comparable {
let priority: Int
static func < (lhs: Document, rhs: Document) -> Bool {
return lhs.priority < rhs.priority
}
}
and call our shuffle like so:
buckets[10].fisherYatesShuffle()
Partition, Sort, Shuffle
As per Josh Homann's suggestion, you could also partition the documents array putting the documents with zero priority after a pivot index. Then sort the first partition, and shuffle the second one :
var result = documents
let pivot = result.partition { $0.priority == 0 }
result[..<pivot].sort(by: > )
result[pivot...].shuffle()
Benchmarks
Here are some benchmarks :
Joakim Danielson's : 1643µs
MartinR's : 169µs
Josh Homann's : 152µs
Orig. Fisher-Yates : 38µs
This : 15µs
(Even though TIO uses Swift 4, relatively comparable results were got using the same code on my local machine with Swift 5, run in the terminal with optimizations)

Select middle value in array - Swift

I'm trying to make sure that the middle value Lists is the first view that is seen when building my application. Xcode offers if let firstView = viewList.first and if let firstView = viewList.last but I can't workout how to select the middle value.
lazy var viewList:[UIViewController] = {
let sb = UIStoryboard(name: "Main", bundle: nil)
let lists = sb.instantiateViewController(withIdentifier: "Lists")
let reminders = sb.instantiateViewController(withIdentifier: "Reminders")
let todo = sb.instantiateViewController(withIdentifier: "To Do")
return [reminders, lists, todo]
}()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
if let firstView = viewList.first {
self.setViewControllers([firstView], direction: .forward, animated: true, completion: nil)
}
}
Similar to first and last, you can extend Array with a computed middle property that returns an optional Element.
extension Array {
var middle: Element? {
guard count != 0 else { return nil }
let middleIndex = (count > 1 ? count - 1 : count) / 2
return self[middleIndex]
}
}
Usage Example:
if let middleView = viewList.middle {
//... Do something
}
I want you to be aware that first and last can return the same element if the array has only 1 element.
Similarly, though this extension will work for any array length, it can return the same element for:
first, middle & last if your array has only 1 element
middle & last if your array has only 2 elements
Can add an extension to Array to accomplish this:
extension Array {
var middleIndex: Int {
return (self.isEmpty ? self.startIndex : self.count - 1) / 2
}
}
let myArray: [String] = ["Hello", "World", "!"]
print("myArray.middleIndex: \(myArray.middleIndex)") // prints 1
Since viewList declared as [UIViewController] (not as optionals - [UIViewController?]), you don't have to "optional binding" (checking the whether the element is nil or not) because it has to be exist. what you should do instead is to check if the index in the range (making sure that the index is in the range).
Logically speaking (obviously), if you are pretty sure that viewList always has 3 elements, there is no need to do any check, just:
let middleViewController = viewList[1]
In case of the number of elements in viewList is undetermined and you are aiming to get the middle element, you simply get it as:
let middleViewController = viewList[(viewList.count - 1) / 2]
Remember, first and last are optionals, in your case there is no need to work with optionals...
Your viewList is an array of type UIViewController. And first and last only denotes their 0th and last index. like:
viewList.first is same as viewList[0]
viewList.last is same as viewList[viewList.count - 1]
Only difference in using these are if you will use viewList.first it will return nil if your array is empty, but if you will use viewList[0] on empty array, you app will be crash with error index out of bound...
So, you can easily access your middle value with index as:
if viewList.count > 1 {
let middleView = viewList[1]
self.setViewControllers([middleView], direction: .forward, animated: true, completion: nil)
}
If you are not sure about the viewList.count will be 3 or more, Then:
let middleIndex = (viewList.count - 1) / 2
let middleView = viewList[middleIndex]

Remove tuple from array of tuples

Func to see if two arrays have some same values:
func hasAllSame(largeCombinedArry:[Int], wantToKeep: [Int])->Bool{
var howManySame = [Int]()
for intElement in largeCombinedArry{
if wantToKeep.contains(intElement){
howManySame.append(intElement)
}
}
howManySame.sort()
if wantToKeep == howManySame{
print("These are the same!")
return true
}
else{
print("These are NOT same!")
return false
}
}
Array of tuples declared like this:
var TuplesArry:[(score: Double, value: [Int])] = []
Array filled thusly:
for (arry1, score) in zip(arryOfArrays, AllScores) {
let calculatedDiff = otherValue - score
TuplesArry.append((score: calculatedDiff, value: arry1))
}
var arrayForComparison = [8,9,7,6]
arrayForComparison.sort()
Error occurs here after several iteration at function call hasAllSame()
for i in 0..<TuplesArry.count{
if hasAllSame(largeCombinedArry: TuplesArry[i].value, wantToKeep:
arrayForComparison){
//do nothing
}
else{
/*
want to remove tuple that does not have all elements that are
in arrayForComparison
*/
TuplesArry.remove(at: i)
}
}
This code seems to be working but it seems like tuplesArry.count continues to decrease and iterator i continues to increase until error occurs "fatal index out of range"
My goal is to remove a tuple from the array of tuples if it's value does not meet criteria.
I've also tried something like:
for tuple in TuplesArry{
if hasAllSame(largeCombinedArry: tuple.value, wantToKeep:
arrayForComparison){
//do nothing
}
else{
//this does NOT work
let index = TuplesArry.index(of:tuple)
TuplesArry.remove(at: index)
}
}
The immediate issue is that you need to iterate in reverse to avoid the "index out of range" issue.
The much simpler solution is to use filter on your array. Then you entire for loop can be replaced with:
TouplesArry = TouplesArry.filter { hasAllSame(largeCombinedArry: $0.value, wantToKeep: arrayForComparison) }

can you have an array with no element at index 0

Im wondering if it is possible to insert an element at index 1 but not index 0 in swift like this:
var array = [String]()
array.insert("cow", atIndex: 1)
but every time I try I get the old fatal error: Array index out of range error message.
Is there anyway around this problem? Any suggestions would be greatly appreciated! Thanks!
If you make it an array of optionals and initialize the number of elements you want first, you can get close.
var array = [String?]()
for i in 0...5 {
array.append(nil)
}
array.insert("cow", atIndex: 1)
If you really want the index to be specific, rather than just the next available position in the array, you should use a dictionary with Int keys.
var dict = [Int:String]()
dict[1] = "Cow"
dict[5] = "Chicken"
You can create a custom list. You will need to add some checking to make sure items aren't null or out of index, etc.
void Main()
{
var list = new CustomList<string>();
list.Add("Chicken");
list.Add("Bear");
list[1] = "Cow";
list[1].Dump(); //output Cow
}
public class CustomList<T>
{
IList<T> list = new List<T>();
public void Add(T item)
{
list.Add(item);
}
public T this[int index]
{
get
{
return list[index - 1];
}
set
{
list[index - 1] = value;
}
}
}
Literally you can't.
An item can be inserted up to the maximum index index(max) = array.count, in case of an empty array at index 0.

How to loop through array to find 4 identical values that are consecutive?

I have the following swift array:
var winSuitArray = [cardSuit1, cardSuit2, cardSuit3, cardSuit4, cardSuit5, cardSuit6, cardSuit7]
cardSuit1, cardSuit2 and so on, are variables that will equal strings like "clubs" or "hearts". What I want to do is loop through this array, and if the loop finds 4 identical objects whose indexes are consecutive, set the winSuitStatus bool to true.
For example, if the array looks like this:
["hearts", "clubs", "clubs", "clubs", "clubs", "diamonds", "spades"]
I want to loop through it like so:
for card in winSuitArray {
//find 4 identical and consecutive objects
// if the above requirements are met, let winSuitStatus = true
}
Is this possible to do?
To tell the truth, I'd probably do something similar to #KnightOfDragon's answer. There's nothing wrong with that approach. But this problem opens up some opportunities to build some much more reusable code at little cost, so it seems worth a little trouble to do that.
The basic problem is that you want to create a sliding window of a given size over the list, and then you want to know if any of the windows contain only a single value. So the first issue to to create these windows. We can do that very generally for all collections, and we can do it lazily so we don't have to compute all the windows (we might find our answer at the start of the list).
extension Collection {
func slidingWindow(length: Int) -> AnyRandomAccessCollection<SubSequence> {
guard length <= count else { return AnyRandomAccessCollection([]) }
let windows = sequence(first: (startIndex, index(startIndex, offsetBy: length)),
next: { (start, end) in
let nextStart = self.index(after: start)
let nextEnd = self.index(after: end)
guard nextEnd <= self.endIndex else { return nil }
return (nextStart, nextEnd)
})
return AnyRandomAccessCollection(
windows.lazy.map{ (start, end) in self[start..<end] }
)
}
}
The use of AnyRandomAccessCollection here is to just hide the lazy implementation detail. Otherwise we'd have to return a LazyMapSequence<UnfoldSequence<(Index, Index), ((Index, Index)?, Bool)>, SubSequence>, which would be kind of crazy.
Now are next question is whether all the elements in a window are equal. We can do that for any kind of Equatable sequence:
extension Sequence where Iterator.Element: Equatable {
func allEqual() -> Bool {
var g = makeIterator()
guard let f = g.next() else { return true }
return !contains { $0 != f }
}
}
And with those two pieces, we can just ask our question. In the windows of length 4, are there any runs that area all equal?
let didWin = suits.slidingWindow(length: 4).contains{ $0.allEqual() }
Or we could go a little different way, and create a SlidingWindowSequence that we could iterate over. The logic here is basically the same. This just wraps up the windowing into a specific type rather than a AnyRandomAccessCollection. This may be overkill for this problem, but it demonstrates another powerful pattern.
public struct SlidingWindowSequence<Base: Collection>: Sequence, IteratorProtocol {
let base: Base
let windowSize: Base.IndexDistance
private var windowStart: Base.Index
public init(_ base: Base, windowSize: Base.IndexDistance) {
self.base = base
self.windowSize = windowSize
self.windowStart = base.startIndex
}
public mutating func next() -> Base.SubSequence? {
if base.distance(from: windowStart, to: base.endIndex) < windowSize {
return nil
}
let window = base[windowStart..<base.index(windowStart, offsetBy: windowSize)]
windowStart = base.index(after: windowStart)
return window
}
}
let didWin = SlidingWindowSequence(suits, windowSize: 4).contains{ $0.allEqual() }
var suit = ""
var count = 1
for card in winSuitArray {
if(suit == card)
{
count++
}
else
{
count = 1
suit = card
}
if(count == 4)
{
//find 4 identical and consecutive objects
// if the above requirements are met, let winSuitStatus = true
}
}
You can use a counter variable to do this, initialized to 1.
for each value in array:
if value equals previous value
increment counter
else
counter = 1
if counter >= 4
set winCounter to true

Resources