Removing from array during enumeration in Swift? - ios

I want to enumerate through an array in Swift, and remove certain items. I'm wondering if this is safe to do, and if not, how I'm supposed to achieve this.
Currently, I'd be doing this:
for (index, aString: String) in enumerate(array) {
//Some of the strings...
array.removeAtIndex(index)
}

In Swift 2 this is quite easy using enumerate and reverse.
var a = [1,2,3,4,5,6]
for (i,num) in a.enumerate().reverse() {
a.removeAtIndex(i)
}
print(a)

You might consider filter way:
var theStrings = ["foo", "bar", "zxy"]
// Filter only strings that begins with "b"
theStrings = theStrings.filter { $0.hasPrefix("b") }
The parameter of filter is just a closure that takes an array type instance (in this case String) and returns a Bool. When the result is true it keeps the element, otherwise the element is filtered out.

In Swift 3 and 4, this would be:
With numbers, according to Johnston's answer:
var a = [1,2,3,4,5,6]
for (i,num) in a.enumerated().reversed() {
a.remove(at: i)
}
print(a)
With strings as the OP's question:
var b = ["a", "b", "c", "d", "e", "f"]
for (i,str) in b.enumerated().reversed()
{
if str == "c"
{
b.remove(at: i)
}
}
print(b)
However, now in Swift 4.2 or later, there is even a better, faster way that was recommended by Apple in WWDC2018:
var c = ["a", "b", "c", "d", "e", "f"]
c.removeAll(where: {$0 == "c"})
print(c)
This new way has several advantages:
It is faster than implementations with filter.
It does away with the need of reversing arrays.
It removes items in-place, and thus it updates the original array instead of allocating and returning a new array.

When an element at a certain index is removed from an array, all subsequent elements will have their position (and index) changed, because they shift back by one position.
So the best way is to navigate the array in reverse order - and in this case I suggest using a traditional for loop:
for var index = array.count - 1; index >= 0; --index {
if condition {
array.removeAtIndex(index)
}
}
However in my opinion the best approach is by using the filter method, as described by #perlfly in his answer.

No it's not safe to mutate arrays during enumaration, your code will crash.
If you want to delete only a few objects you can use the filter function.

Either create a mutable array to store the items to be deleted and then, after the enumeration, remove those items from the original. Or, create a copy of the array (immutable), enumerate that and remove the objects (not by index) from the original while enumerating.

The traditional for loop could be replaced with a simple while loop, useful if you also need to perform some other operations on each element prior to removal.
var index = array.count-1
while index >= 0 {
let element = array[index]
//any operations on element
array.remove(at: index)
index -= 1
}

I recommend to set elements to nil during enumeration, and after completing remove all empty elements using arrays filter() method.

Just to add, if you have multiple arrays and each element in index N of array A is related to the index N of array B, then you can still use the method reversing the enumerated array (like the past answers). But remember that when accessing and deleting the elements of the other arrays, no need to reverse them.
Like so, (one can copy and paste this on Playground)
var a = ["a", "b", "c", "d"]
var b = [1, 2, 3, 4]
var c = ["!", "#", "#", "$"]
// remove c, 3, #
for (index, ch) in a.enumerated().reversed() {
print("CH: \(ch). INDEX: \(index) | b: \(b[index]) | c: \(c[index])")
if ch == "c" {
a.remove(at: index)
b.remove(at: index)
c.remove(at: index)
}
}
print("-----")
print(a) // ["a", "b", "d"]
print(b) // [1, 2, 4]
print(c) // ["!", "#", "$"]

Related

Is there a way to re-fill the array with new value, without using for loop?

In Java, we can fill the array by simply
String[] strings = new String[10];
java.util.Arrays.fill(strings, "hello");
// Re-fill the array with "bye" value.
java.util.Arrays.fill(strings, "bye");
But, how do we perform similar thing in Swift? The closest I can get is
var strings = [String](repeating: "hello", count: 10)
// Re-fill the array with "bye" value.
for index in strings.indices {
strings[index] = "bye"
}
I want the avoid the following, as it will create another new instance of array.
var strings = [String](repeating: "hello", count: 10)
strings = [String](repeating: "bye", count: 10)
Is there a way to re-fill the array with new value, without using for loop?
Swift doesn't provide such a function by default but Swift is a pretty well expandable language.
The closest function is replaceSubrange(_:with:) so you could write an extension of Array
extension Array {
mutating func fill(withValue value: Element) {
replaceSubrange(startIndex..., with: [Element](repeating: value, count: count))
}
}
And use it
var strings = [String](repeating: "hello", count: 10)
strings.fill(withValue: "bye")
The effort to avoid a loop is just syntactic sugar. Almost all of these functions use a loop under the hood.

Will appending array's object in array will maintain object sort order every time?

I'm doing some operations like sorting, filter and grouped by some attributes of arrays object.
I'm adding objects of a filtered array in to another array like:
arrGroup.append(contentsOf: filteredArray)
My question is: will all of the objects maintain the same sorted order in the array every time, with 100% certainty?
Logically, will it add the object like
for object in filteredArray {
arrGroup.append(object)
}
or
for index in 0...filteredArray.count {
let object = filteredArray[index]
arrGroup.append(object)
}
For me, all are same, just difference in CPU cycle at run time. But my friend says that I should go with last option. Technically I'm getting same result for all three every time I debug my code.
Your suggestion please.
Yes, when you add an Array to another Array it will maintain the order as it is.
But yes if you are using Set then you might not get same order as it is not ordered collection but Array is ordered collection which maintains it's ordering until you changes it manually.
here is the code example :
var arr1 = ["1", "2" , "3"] // Print arr1 : ["1", "2", "3"]
let arr2 = ["4", "5" , "6"] // Print arr2 : ["4", "5", "6"]
arr1.append(contentsOf: arr2) // Print arr1 : ["1", "2", "3", "4", "5", "6"]
Array preserves whatever ordering you give it.
Array.append(contentsOf:) appends all items of the second array to the end of the first array, in order. Here's roughly what that algorithm would look like:
extension Array {
mutating func myAppend(contentsOf other: [Element]) {
reserveCapacity(self.count + other.count)
for element in other {
self.append(element)
}
}
}
Techniques for iterating an array
If you only need the elements
The preferred method
The preferred way to iterate the items of a Sequence is to use a typical for-in loop:
for element in array { // most preferred!
// use the element
}
The discouraged method
for i in 0 ..< array.count {
let element = array[i] // Avoid this!
// use the element
}
I highly advise against this technique. The reason is because it's very easy to fall victim to an off-by-one-error. In fact, your very own example has it!
for index in 0...filteredArray.count {
let object = filteredArray[index] // when index is filteredArray.count ... 💣
arrGroup.append(object)
}
Don't use this! Any array of n elements has indices 0 ..< n, not 0 ... n. Attempting to access array[array.count] will crash your program.
Another valid but discouraged method
for i in array.indices {
let element = array[i] // Avoid this!
// use the element
}
If you only need the indices
for i in array.indices {
// use the index i
}
If you need both the indices and the elements
for (i, element) in array.enumerated() {
// use the index i and the element.
}
The 2 for loops you have above are doing the same thing in terms of they will iterate from object #0 to the last object.
The first one is called fast enumeration and hence faster and more effective than the second.
And to answer your question. Yes the order will remain the same.

How do you remove an element from an array without the index value? [duplicate]

This question already has answers here:
How to remove single object in array from multiple matching object
(3 answers)
Closed 6 years ago.
Does anyone know how to remove an element from an array without knowing exactly what spot it is in?
var array = ["A", "B", "C"]
how would I remove the "A" if there were only a few in the whole array and it contained thousands of strings(Just remove one "A" not all of them)?
Just like this:
var array = ["A", "B", "C"]
if let firstIndex = array.indexOf("A") { // Get the first index of "A"
array.removeAtIndex(firstIndex) // Remove element at that index
}
Swift 3:
if let firstIndex = array.index(of: "A") {
array.remove(at: firstIndex)
}

How to sort array based on another arrays position?

I have a tableView with its style being Right Detail. I therefore have 2 arrays, one is for the textLabels data, and the other is for detailTextLabel.
There will be 2 "sort by" options. One will be sort by the textLabels data, and the second will sort by the detailTextlabels data. So when I sort the first array (textLabels array), the second array (detailTextLables array) will also have to get sorted based on the firstarray`.
I know how to sort arrays, but how can I sort one array based on another?
Here's how I sorted the array: (it's an array of Dates.
firstArray.sort({ (a, b) -> Bool in
a.earlierDate(b) == a
})
First, why not have two arrays? Because you only have one array of UITableViewCells and you want to keep all the data associated with a particular table view cell together. And it makes the need to try to coordinate the sorting of multiple arrays (what you are asking to do) unnecessary.
But if you really want to do that:
var array1 = ["1", "3", "2"]
var array2 = ["One", "Three", "Two"]
let sortedFoo = zip(array1, array2).sort { $0.0 < $1.0 }
array1 = sortedFoo.map { $0.0 }
array2 = sortedFoo.map { $0.1 }
The idea with the above code is that it combines the two arrays into one array of tuples, and then sorts them based on the elements in the first array, then breaks that single array back out into two separate arrays.
In other words, since you have to combine the two arrays into one to do the sort anyway, you might as well make them in a single array in the first place. :-)
It's a bit messy, but you can use enumerate to work with indices and elements at the same time:
array1.enumerate().sort {
return $0.element < $1.element
}.map {$0.element}
array1.enumerate().sort {
return array2[$0.index] < array2[$1.index]
}.map {$0.element}
But it's really much simpler/easier with one array.
struct Item {
let prop1: Int
let prop2: String
}
var array = [
Item(prop1: 1, prop2: "c"),
Item(prop1: 2, prop2: "b"),
Item(prop1: 3, prop2: "a")
]
array.sort { $0.prop1 < $1.prop1 }
array.sort { $0.prop2 < $1.prop2 }
How about using using positionOf on the sorted array to find the corresponding index in the I sorted array?

Remove an element in an array without hard-coding the index? in Swift

This is my first post, and I am very happy to join in this community. I am learning Swift with Ray Wenderlich's video tutorial. The challenge I got for lesson 3 is remove a element in an array without hard-coding the index. I understand the correct answer that Ray provided, but just don't understand why my answer can not work. Please see following for Ray's answer as well as my answer. If anyone can explain it for me, that would be great!! thanks:]
Correct Answer:
// Make an array with "C", "C++", and "Objective-C"
var programmingLanguages = ["C", "C++", "Objective-C"]
// Append "Swift" to the array
programmingLanguages += "Swift"
// Insert "Javascript" at Index 2
programmingLanguages.insert("Javscript", atIndex: 2)
// Remove "Objective-C" (without hard-coding the index)
let optIndex = find(programmingLanguages, "Objective-C")
if let defIndex = optIndex {
programmingLanguages.removeAtIndex(defIndex)
}
programmingLanguages
My answer1:
// Make an array with "C", "C++", and "Objective-C"
var programmingLanguages = ["C", "C++", "Objective-C"]
// Append "Swift" to the array
programmingLanguages += "Swift"
// Insert "Javascript" at Index 2
programmingLanguages.insert("Javscript", atIndex: 2)
// Remove "Objective-C" (without hard-coding the index)
programmingLanguages.removeAtIndex(find(programmingLanguages,"Objective-C")
programmingLanguages
My answer2:
// Make an array with "C", "C++", and "Objective-C"
var programmingLanguages = ["C", "C++", "Objective-C"]
// Append "Swift" to the array
programmingLanguages += "Swift"
// Insert "Javascript" at Index 2
programmingLanguages.insert("Javscript", atIndex: 2)
// Remove "Objective-C" (without hard-coding the index)
let optIndex = find(programmingLanguages, "Objective-C")
programmingLanguages.removeAtIndex(optIndex)
programmingLanguages
When you use find() it is not guaranteed to return a result (what happens here?: find(programmingLanguages, "rick astley"))
To account for this possibility find() returns an "optional" - which basically just means that when it does not find a match it will return nil.
In order to protect you from accidentally using nil in some place that does not know how to handle it, the language considers optionals to be a distinct type. In short, you must check for nil when you get an optional value.
The main way to do that is with "if let" syntax – as seen in Ray's example – but a more terse (and more common) way to do it is like so:
if let defIndex = find(programmingLanguages, "Objective-C") {
programmingLanguages.removeAtIndex(defIndex)
}
if you know for sure that the optional is not nil (like if you are checking for something you just inserted) you can use "forced unwrapping" which grabs the value out of the optional (and will cause an error and crash if it's nil).
You do forced unwrapping using the forced unwrapping operator, the exclamation point !:
// defIndex is an optional
let defIndex = find(programmingLanguages, "Objective-C")
// defIndex! <-- unwraps the optional but raises an error if it's nil
programmingLanguages.removeAtIndex(defIndex!)
Your answer doesn't work because find returns an optional value (the type is Int? and not Int), primarily so that if the the value is not found, it returns nil, if the value is found it returns the index wrapped in an optional value. Your answer then attempts to pass that optional value to removeAtIndex, which takes a Int parameter, not an Int?.
Ray's answer conditionally unwraps the result and checks it's validity before attempting to pass it on to removeAtIndex.
While I think Jiaaro nails it brevity-wise, here's a function I wrote today that does something similar. I didn't know about the find function and while longer, it's easy to follow and might help you get the concept.
func arrayByRemovingObjectIdenticalTo<T: Equatable>(objectToRemove: T, fromArray:[T]) -> [T] {
var returnArray = fromArray
for (index, object) in enumerate(fromArray) {
if object == objectToRemove {
returnArray.removeAtIndex(index)
}
}
return returnArray
}

Resources