Array#extract_if
A handy Array method that essentially finds and removes an item from an array in one go
class Array
def extract_if( &block )
if n = index(&block)
slice!(n)
end
end
end
Use cases are numerous. Here's one : you have a list of random links but you want wikipedia to always appear first.
@links = [ 'random.com/bla', '...', 'wikipedia.com/bla', '...']
# show.html.erb
<%= @links.extract_if {|link| link.matches/wikipedia.com/ } %> -
<%= @links.join(" - ") %>
Written by Charles Sistovaris
Related protips
10 Responses
"A handy Array method that essentially finds and removes an item from an array in one go"
Ruby on its own already has this (Array#reject!) :
@links.reject!{|link| link.match /wikipedia.com/ }
</code>
Array#reject! will return the array stripped out of the selection.
a = [1, 2, 3, 4, 5]
a.reject! {|i| i > 2} #=> [1, 2]
Array#extract_if
will return the first item found and return it (like detect & find) but will also slice it out from the array (like pop or unshift)
a = [1, 2, 3, 4, 5]
a.extract_if { |i| i > 2 } #=> 3
a #=> [1, 2, 4, 5]
I looked it up closely, there's no equivalent in Ruby core nor ActiveSupport. Not even the venerable Facets !
You have a few typos in your example. If this was your goal - "Here's one : you have a list of random links but you want wikipedia to always appear first." , then your example needs to be rewritten like this.
@links = [ 'random.com/bla','...', 'wikipedia.com/bla',' ...', 'random2.com']
@wiki= @links.extract_if {|link| link.match(/wikipedia.com/) }
@links.unshift(@wiki)
@links.join(" - ")
This can be futher optimized of course
Thanks, that looks better.
However my general use case would be rather treating the extracted item separately from the rest - not putting it back in the array.
But this raises a good question : is there any Array method which puts an item in the first position ? Will investigate...
Will it be as efficient as #reject! method?
If you really want to delete items from existing array, it makes sense to do it with linked list. I suspect that deleting item from ruby array shifts the other elements to occupy the empty slot?
I'd expect extract_if
to extract all element matched, not just the first.
In the example, I used to do:
links.sort_by { |link| link.match(/wikipedia.com/) ? 0 : 1 }.join(' - ')
Of course, if I don't care to the order of the links as long as wikipedia appears first.
And I agree with deradon and oelmekki, I would call this method "extract_first!"
@thehappycoder whenever I find some time to benchmark I'll share it - but reject! doesn't achieve the same thing : see prev comment.
@deradon & @otaviocardoso : agreed #extract_first! is better. But how about a simple #extract! - which I used in the first place ? Also unshift & pop don't have a bang so I wonder if it's truly necessary (naming...)
@otaviocardoso : good solution for putting an item in the first position. I wonder how it compares to extract + unshift, performance wise. Also one reason you'd extract instead of sort is to treat it differently e.g. call the extracted link a generic "wikipedia" instead of the full url - (in my use case the links are actually AR Link objects... )
@charly : I think #extract! is simpler, but it could make the same confusion that @deradon points, people expect that it is the same that #reject!, extracting all elements matched. And about the bang... Actually, this makes me think why #delete and his "brothers" does not have the bang.
And I agree with you. My solution is just for this "simplest" case, when you don't have to do any different treatment with the extracted link, you don't care about the ordering of the links but the first, etc.
my previous comment seems to have taken a walk.
otaviocardoso's answer gives you the gist of it.