Reputation: 2897
I am attempting to remove ""
& " "
from the back of a string array until the last item contains some text, but my implementation isn't picking up " "
.
My implementation so far:
var array = ["A", "B", "", "C", "D", " ", " ", ""]
while true {
if (array.last == " " || array.last == "") {
array.removeLast()
} else {
break
}
}
My desired output is
["A", "B", "", "C", "D"]
, but my current output is
["A", "B", "", "C", "D", " ", " "]
, where the while
loop simply breaks
after encountering " "
Any advice why is it not picking up the " "
?
Upvotes: 5
Views: 2180
Reputation: 32863
For completeness, here's a one liner, functional programming style, extension, that removes the last elements that satisfy the given criteria:
extension Collection {
func dropLast(while predicate: (Element) -> Bool) -> SubSequence {
indices.reversed().first { !predicate(self[$0]) }.map(prefix(through:)) ?? prefix(0)
}
}
Same code, but in a more imperative style:
extension Collection {
func dropLast(while predicate: (Element) -> Bool) -> SubSequence {
if let lastValidIndex = indices.reversed().first(where: { !predicate(self[$0]) }) {
return prefix(through: lastValidIndex)
} else {
return prefix(0)
}
}
}
Upvotes: 0
Reputation: 236458
I don't know why they have drop(while:)
and did not implement dropLast(while:)
. The implementation bellow works on any collection:
extension Collection {
func dropLast(while predicate: (Element) throws -> Bool) rethrows -> SubSequence {
guard let index = try indices.reversed().first(where: { try !predicate(self[$0]) }) else {
return self[startIndex..<startIndex]
}
return self[...index]
}
}
"123".dropLast(while: \.isWholeNumber) // ""
"abc123".dropLast(while: \.isWholeNumber) // "abc"
"123abc".dropLast(while: \.isWholeNumber) // "123abc"
And extending RangeReplaceableCollection we can implement remove(while:)
and removeLast(while:)
as well:
extension RangeReplaceableCollection {
mutating func remove(while predicate: (Element) throws -> Bool) rethrows {
guard let index = try indices.first(where: { try !predicate(self[$0]) }) else {
removeAll()
return
}
removeSubrange(..<index)
}
mutating func removeLast(while predicate: (Element) throws -> Bool) rethrows {
guard let index = try indices.reversed().first(where: { try !predicate(self[$0]) }) else {
removeAll()
return
}
removeSubrange(self.index(after: index)...)
}
}
var string = "abc123"
string.removeLast(while: \.isWholeNumber)
string // "abc"
var string2 = "abc123"
string2.remove(while: \.isLetter)
string2 // "123"
var array = ["A", "B", "", "C", "D", " ", " ", ""]
array.removeLast { $0 == "" || $0 == " " }
array // ["A", "B", "", "C", "D"]
Upvotes: 5
Reputation: 20274
Just for fun, lets extend Array
with this functionality in a generic way while also externally providing the condition for more flexibility.
Similar to Array
s having a drop(while:)
, we can make a dropLast(while:)
like so:
extension Array {
func dropLast(while handler: (Element)->Bool) -> Array {
var array = self
while let last = array.last, handler(last) {
array.removeLast()
}
return array
}
}
let array = ["", "A", "B", "", "C", "D", " ", " ", ""]
let modified = array.dropLast { $0.trimmingCharacters(in: .whitespaces).isEmpty }
print(modified) //["", "A", "B", "", "C", "D"]
It can handle other types of arrays too, and since the condition is not baked into the functionality, it's flexible and reusable.
let array = [0, 1, 2, 3, 0, 5, 6, 7, 0, -1, 0, -2]
//Drop (from tail) all numbers less than 1
let modified = array.dropLast(while: { (val) -> Bool in
return val < 1
})
print(modified) //[0, 1, 2, 3, 0, 5, 6, 7]
Upvotes: 4
Reputation: 3256
Move your condition to while
and make sure you're checking on the correct array after the operation.
var array = ["A", "B", "", "C", "D", " ", " ", ""]
while array.last == " " || array.last == "" {
array.removeLast()
}
print(array) // ["A", "B", "", "C", "D"]
Upvotes: 4
Reputation: 92384
One way to solve this is to reverse the collection (which is done lazily) and drop the unwanted items until you encounter the wanted ones. Afterwards, reverse the collection again.
let array = ["A", "B", "", "C", "D", " ", " ", ""]
let filtered = array.reversed().drop(while: {
$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}).reversed() as [String]
print(filtered) // "["A", "B", "", "C", "D"]\n"
Note that the check for " "
may fail if it's not a normal space, for example a non-breaking space (Unicode checkpoint U+00A0). This may be the issue you're having in the first place. So trim the string (it removes characters from the start and end only) and check whether the result is an empty string.
Upvotes: 4
Reputation: 535
Basically your solution plays fine. But you can make it more generic for "", "
", "", "
", .... :
import Foundation
var array = ["A", "B", "", "C", "D", " ", " ", ""]
while true {
let shouldRemoveLast = array.last?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ?? false
if (shouldRemoveLast) { array.removeLast() } else { break }
}
Upvotes: 1