I recently started a thread over in Evolution about this, but figured I would turn it into an actual pitch to solicit further feedback about including this method in the Standard Library.
The motivation is that frequently, when removing elements from some Collection
, we want to retrieve the elements that were removed. Currently, this is not possible to do in bulk with methods in the standard library, since removeAll(where:)
does not return the removed items. This idiom is already valuable enough to be included in the Standard Library in several places: RanceReplaceableCollection
returns Self.Element
from remove(at:)
, Set
returns Element?
from remove(_:)
and update(with:)
. Per @jrose on my Evolution thread, removeAll(where:)
neglects to return the removed items in the interest of efficiency, not for any philosophical reason (if removeAll(where:)
were modified to return these elements, it would always be performing an extra allocation even in situations where the result was not desired.
This idea has been brought up before with a positive, but limited response. Based on this thread (as well as the suggestion from @xwu, I propose the addition of a method with the following signature to RangeReplaceableCollection
:
@discardableResult mutating func extract(where shouldBeExtracted: (Self.Element) throws -> Bool) -> [Self.Element] rethrows
The extract(where:)
method would iterate through the collection, removing elements for which shouldBeExtracted
returns true, collecting them in the array, and returning the array upon completion for further processing.
For a use case, consider the small example of a list of pending tasks which is periodically searched for tasks which are ready to be executed. Using extract(where:)
, this could be implemented as follows:
let readyTasks = taskList.extract(where: { $0.isReady })
readyTasks.forEach { $0.execute() }
Proposed alternatives in the Evolution thread using removeAll(where:)
are less readable, less efficient, or perform consuming work in the shouldBeRemoved
predicate:
// Poor readability
var x = (0...10).shuffled()
let n = x.partition{$0 < 5}
print(x[0..<n]) // Everything 5 and above
print(x[n...]) // Everything less than 5
// Most correct, but highly verbose compared to extract(where:)
var readyTasks: [Task] = []
taskList.removeAll(where: {
let isReady = $0.isReady
if isReady { readyTasks.append($0) }
return isReady
})
readyTasks.forEach { $0.execute() }
// Invokes execute() as part of an inclusion predicate--surprising!
// Also, not feasible if processing of the removed elements array as a whole
// is desired.
taskList.removeAll(where: {
let isReady = $0.isReady
if isReady { $0.execute() }
return isReady
})
The implementation of extract(where:)
can be very similar to that of removeAll(where:)
except that the removed suffix would be copied into an array. This addition would also open up the door to extractSubrange(_:)
and possibly extractLast(_:)
and extractFirst(_:)
.
I've run into the need for this method several times, so I would love to hear if it is a common enough use case to consider inclusion in the Standard Library. If it seems like there is a decent response, I can start of a formal proposal soon.
EDIT: As suggested by @Avi, a better signature removes the @discardableResult
attribute, so that the signature is just:
mutating func extract(where shouldBeExtracted: (Self.Element) throws -> Bool) -> [Self.Element] rethrows