6
\$\begingroup\$

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[0] ? 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?)

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

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...

\$\endgroup\$
2
\$\begingroup\$

it is built-in :

ary.delete_if( &:odd? )

EDIT

Sorry, misread your question. You can do this :

deleted = ary.select( &:odd? ).tap{|odd| ary -= odd }
\$\endgroup\$
  • \$\begingroup\$ 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... \$\endgroup\$ – m_x Apr 30 '13 at 12:22
1
\$\begingroup\$

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]
\$\endgroup\$
1
\$\begingroup\$

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]
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.