# A pattern to destructively extract items from an array

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

• deleted = ary - ary.select(&:someproc) seems totally fine to me – UncleLaz Apr 30 '13 at 11:22

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 }

• as a side note, it is sad that Enumerable#drop does not provide this functionality : it would accept a block, drop n or all elements where the block returns true, and return the dropped elements. I wish I knew C better to patch it... – m_x Apr 30 '13 at 12:22

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]