Ruby Select and Reject in One Method

Ruby Select and Reject in one method

Looks as if Enumerable.partition is exactly what you are after.

= Enumerable.partition

(from ruby core)
------------------------------------------------------------------------------
enum.partition {| obj | block } -> [ true_array, false_array ]
enum.partition -> an_enumerator

------------------------------------------------------------------------------

Returns two arrays, the first containing the elements of enum for
which the block evaluates to true, the second containing the rest.

If no block is given, an enumerator is returned instead.

(1..6).partition {|i| (i&1).zero?} #=> [[2, 4, 6], [1, 3, 5]]

Interesting, I didn't know that was there. ri is an amazing tool...

How to reject or select based on more than one condition in Ruby

You can use dig, which can access to nested keys in a hash, it returns nil if it can't access to at least one of them, then use the safe operator to do the comparison:

p pages.select { |page| page.dig('content', 'score')&.> 75 }
# [{"id"=>100, "content"=>{"score"=>100}}]

Notice this "filter" is done in one step, so you don't need to mutate your object.

For your make-belive approach, you need to replace the returns with next:

pages = pages.select do |page|
next unless page['content']
next unless page['content']['score']
page['content']['score'] > 75
end

p pages # [{"id"=>100, "content"=>{"score"=>100}}]

There next is used to skip the rest of the current iteration.


You can save one line in that example, if you use fetch and pass as the default value an empty hash:

pages.select do |page|
next unless page.fetch('content', {})['score']

page['content']['score'] > 75
end

Same way you can do just page.fetch('content', {}).fetch('score', 0) > 75 (but better don't).

In Ruby, does reject or select combined with each results in multiple iterations?

You can use Enumerator::Lazy to process it in one iteration:

arr.lazy.reject { |e| e == :c }.each { |e| handle(e) }

This will also change the order of invocation. The first element is being processed by each block, then the second element and so on:

arr.lazy.reject { |e|
puts "filtering #{e}"; e == :c
}.each { |e|
puts "handling #{e}"
}

Output:

filtering a
handling a
filtering b
handling b
filtering c # <- c doesn't make it to the 2nd block
filtering d
handling d

The non-lazy approach passes all elements to the first block and the results to the second block:

arr.reject { |e|
puts "filtering #{e}"; e == :c
}.each { |e|
puts "handling #{e}"
}

Output:

filtering a
filtering b
filtering c
filtering d
handling a
handling b
handling d

In Ruby, what - technically - does array.reject or array.select without a block do?

A block is optional for many Ruby methods. When no block is given an enumerator is usually returned. There are at least a couple of reasons you might want an enumerator.

#1 Use the enumerator with the methods in the class Enumerator.

Here's an example. Suppose you wish to alternate the case of letters in a string. One conventional way is:

"oh happy day".each_char.with_index.map { |c,i| i.odd? ? c.upcase : c.downcase }.join
#=> "oH HaPpY DaY"

but you could instead write:

enum = [:odd, :even].cycle
"oh happy day".each_char.map { |c| enum.next==:odd ? c.upcase : c.downcase }.join

or perhaps

enum = [:upcase, :downcase].cycle
"oh happy day".each_char.map { |c| c.send(enum.next) }.join

See the docs for Array#cycle and Enumerator#next.

#2 Use enumerators to chain methods

In my first example above, I wrote:

"oh happy day".each_char.with_index.map...

If you examine the docs for String#each_char and Enumerator#with_index you will see that both methods can be used with or without a block. Here they are both used without a block. That enables the three methods to be chained.

Study the return values in the following.

enum0 = "oh happy day".each_char
#=> #<Enumerator: "oh happy day":each_char>
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: "oh happy day":each_char>:with_index>
enum2 = enum1.map
#=> #<Enumerator: #<Enumerator: #<Enumerator:
# "oh happy day":each_char>:with_index>:map>

You might want to think of enum1 and enum2 as "compound" enumerators.

You show the return value of:

[nil, false, true].reject

to be:

#=> [nil, false, true]

but that is not correct. The return value is:

#<Enumerator: [nil, false, true]:reject>

If we write:

enum = [nil, false, true].reject

then:

enum.each { |e| e }
#=> [nil, false]

(which, since Ruby v2.3, we could write enum.reject(&:itself)). This uses the method Enumerator#each, causing enum to invoke Array#each because reject's receiver is an instance of the class Array.

Method working only with .select or .reject but not working with .map solution

The major difference between your solution and theirs, is that you are sorting filtered_arrays (which is an array of arrays) but you are required to sort each of the arrays inside filtered_arrays. You can simply change return filtered_array.sort to return filtered_array.map { |array| array.sort } or return filtered_array.map(&:sort).

By the way, it's not a good practice to use map when you don't make use of the return value. You can replace map with each and your code will work the exact same.

ruby array (enumerable) method to select and reject into 2 arrays in 1 operation

Sure, you can do:

odd, even = list.partition &:odd?

In Ruby, is there an Array method that combines 'select' and 'map'?

I usually use map and compact together along with my selection criteria as a postfix if. compact gets rid of the nils.

jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}    
=> [3, 3, 3, nil, nil, nil]

jruby-1.5.0 > [1,1,1,2,3,4].map{|n| n*3 if n==1}.compact
=> [3, 3, 3]

Python list comprehension = Ruby select / reject on index rather than element

The method order is relevant:

arr.each_with_index.select { |e, i| i % 3 == 0 }
#=> [[10, 0], [40, 3], [70, 6], [100, 9]]

versus:

arr.select.each_with_index { |e, i| i % 3 == 0 }
#=> [10, 40, 70, 100]

Since select returns an enumerator, you could also use Enumerator#with_index:

arr.select.with_index { |e, i| i % 3 == 0 }
#=> [10, 40, 70, 100]

Regarding your slice equivalent, you can use map (or its alias collect) to collect the items in an array:

(0..arr.length).step(3).map { |e| arr[e] }
#=> [10, 40, 70, 100]

or values_at to fetch the items at the given indices:

arr.values_at(*(0..arr.length).step(3))
#=> [10, 40, 70, 100]

* turns the argument into an array (via to_a) and then into an argument list, i.e.:

arr.values_at(*(0..arr.length).step(3))
arr.values_at(*(0..arr.length).step(3).to_a)
arr.values_at(*[0, 3, 6, 9])
arr.values_at(0, 3, 6, 9)

Slightly shorter:

arr.values_at(*0.step(arr.size, 3))
#=> [10, 40, 70, 100]

Why does Hash#select and Hash#reject pass a key to a unary block?

It's actually passing two arguments, always.

What you're observing is merely the difference between how procs and lambdas treat excess arguments. Blocks (Procs unless you tell Ruby otherwise) behave as if it had an extra splat and discard excess arguments, whereas lambdas (and method objects) reject the caller due to the incorrect arity.

Demonstration:

>> p = proc { |e| p e }
=> #<Proc:0x007f8dfa1c8b50@(irb):1>
>> l = lambda { |e| p e }
=> #<Proc:0x007f8dfa838620@(irb):2 (lambda)>
>> {a: 1}.select &p
:a
=> {:a=>1}
>> {a: 1}.select &l
ArgumentError: wrong number of arguments (2 for 1)
from (irb):2:in `block in irb_binding'
from (irb):4:in `select'
from (irb):4
from /usr/local/bin/irb:11:in `<main>'

As an aside, since it was mentioned in the comments: map, in contrast, actually passes one argument. It gets allocated to two different variables because you can assign multiple variables with an array on the right side of the assignment operator, but it's really one argument all along.

Demonstration:

>> {a: 1}.map { |k, v| p k, v }
:a
1
>> {a: 1}.map &p
[:a, 1]
=> [[:a, 1]]
>> {a: 1}.map &l
[:a, 1]

And upon changing p and l defined further up:

>> p = proc { |k, v| p k, v }
=> #<Proc:0x007ffd94089258@(irb):1>
>> l = lambda { |k, v| p k, v }
=> #<Proc:0x007ffd940783e0@(irb):2 (lambda)>
>> {a: 1}.map &p
:a
1
=> [[:a, 1]]
>> {a: 1}.map &l
ArgumentError: wrong number of arguments (1 for 2)
from (irb):2:in `block in irb_binding'
from (irb):4:in `each'
from (irb):4:in `map'
from (irb):4
from /usr/local/bin/irb:11:in `<main>'

Destructive reject from an array returning the values rejected

You can build your own method for this...

class Array
def extract(&block)
temp = self.select(&block)
self.reject!(&block)
temp
end
end

then...

a = [1, 2, 3, 4, 5]
a.extract{|x| x < 3}
=> [1,2]
p a
=> [3, 4, 5]

EDIT: If you don't want to monkey patch (but monkey patching isn't evil in itself) you can do it with a vanilla method...

def select_from_array(array, &block)
temp = array.select(&block)
array.reject!(&block)
temp
end

array = [1,2,3,4,5,6,7,8]

less_than_three = select_from_array(array){|v| v<3}
=> [1,2]
array
=> [3,4,5,6,7,8]
more_than_five = select_from_array(array){|v| v>5}
=> [6,7,8]
array
=> [3,4,5]


Related Topics



Leave a reply



Submit