# A pattern to destructively extract items from an array

Posted on

Problem

I want to efficiently (few intermediate objects) and neatly (few lines and easy to read) filter an array, removing and returning rejected items. So like a call to delete_if except instead of returning the remaining items, returning the deleted items.

Here’s some code that works:

``````ary = (0..9).to_a
odds = ary.select {|i| i%2==1 }
ary -= odds
``````

or:

``````ary = (0..9).to_a
(_,ary),(_,odds) = ary.group_by {|i| i.odd? }.sort {|a,b| a ? 1 : -1 }
``````

But I can’t help but think there should be a better way…

Ideally something more like: `odds = ary.delete_and_return(&:odd?)`

Solution

`Array.partition` returns an array of two arrays – one where the elements returned `true` to the condition, and one that returned `false`.

So, a single liner for your need would be:

``````odds, ary = (0..9).to_a.partition(&:odd?)
=> [[1, 3, 5, 7, 9], [0, 2, 4, 6, 8]]
``````

`(&:odd?)` acts exactly like writing `{ |x| x.odd? }` which will return true if the number is, well, odd…

it is built-in :

``````ary.delete_if( &:odd? )
``````

EDIT

``````deleted = ary.select( &:odd? ).tap{|odd| ary -= odd }
``````

You need `#partition`:

``````irb> a = (1..9).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
irb> a.partition(&:odd?).tap{ |y, n| a = n }.first
=> [1, 3, 5, 7, 9]
irb> a
=> [2, 4, 6, 8]
``````

Since there is no such method yet in ruby, you can always add your own if that makes sense to your use case (if you find yourself needing that method a lot).

Here is and example implementation:

``````class Array
def extract! &block
extracted = select(&block)
reject!(&block)
extracted
end
end
``````

The code above first selects what you want to return and then destructively removes those items which is what you want to avoid doing manually to improve readability.

Note: the method is called `extract!` with a bang since it modifies the array itself. If the method wuould not modify the underlying object, it would be equivalent to `select`, so you might name it `select!`, or `select_and_remove!` if you prefer.

Usage:

``````a = (0..10).to_a

# extract even numbers from our array
p a.extract!(&:even?) # => [0, 2, 4, 6, 8, 10]

# odd numbers are left
p a # => [1, 3, 5, 7, 9]
``````