Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove last elements of a collection while a condition in Swift

Tags:

swift

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 " "?

like image 873
Koh Avatar asked Dec 02 '22 09:12

Koh


2 Answers

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"]
like image 187
Leo Dabus Avatar answered Dec 06 '22 23:12

Leo Dabus


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.

like image 29
DarkDust Avatar answered Dec 07 '22 01:12

DarkDust