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 return
s 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
What Is the Most Elegant Way in Ruby to Remove a Parameter from a Url
How to Tell or Hint to Rubymine What Type a Local or Instance Variable Is
No Source for Ruby-2.0.0-P451 Provided with Debugger-Ruby_Core_Source Gem
In Ruby What Is the Meaning of Colon After Identifier in a Hash
How to Globally Ignore Invalid Byte Sequences in Utf-8 Strings
Ruby String with Usd "Money" Converted to Number
How to Enable Compression in Ruby on Rails
"Use" Keyword/Word in Ruby/Rails/Rack Code
Active Admin: Sorting on Multiple Columns
Are There Any Ruby Orms Which Use Cursors or Smart Fetch
Xpath Parent Attribute of Selection
Sending a Delete Request from Sinatra
Run a Cli Thor App Without Arguments or Task Name
How to Make the Url's in Ruby on Rails Seo Friendly Knowing a @Vendor.Name