2

I know how select, collect and map work in Ruby. I was just wondering if there is a similar native Ruby method that can combine them & give results in a single iteration without having the need to remove nil from array?

For ex.

(1..10).map { |x| x*3 if x.even? }.compact
(1..10).select { |x| x.even? }.map{ |x| x*3 }
(1..10).select { |x| x.even? }.collect{ |x| x*3 }

all give same result, which is [6, 12, 18, 24, 30]. But is there 'some_method' gives the same result?

(1..10).some_method { |x| x*3 if x.even? }  ## evaluates to [6, 12, 18, 24, 30]
Utsav Kesharwani
  • 1,595
  • 10
  • 21
  • 1
    The second two examples can be shortened, e.g. `(1..10).select(&:even?).map{ |x| x*3 }`, which makes them pretty pithy. – Wayne Conrad Jan 24 '14 at 19:12
  • 4
    Ruby, like most languages, gives us the basic building blocks, which we use to build more complicated functionality. When we want to do something more complex, but hide the complexity, we define a method to do that. A lot of Ruby's built-in methods do just that; Look at their source, and you'll see the primitives being used to add additional creature-comforts, because someone saw the need. – the Tin Man Jan 24 '14 at 19:17
  • 3
    Is there any particular reason you want to use one method instead of two here? IMO, all of these examples are perfectly clear, and unless you're running them on a massive array performance isn't likely to be a major concern. – Ajedi32 Jan 24 '14 at 19:17
  • 1
    From my own experience, if you have to use `compact` to clean up a `map` operation, odds are *really* good you're doing something wrong. A `map` should emit a useful and expected return value for every value passed in. – the Tin Man Jan 24 '14 at 19:22
  • @Ajedi32 - I don't have a large array to operate on, but just was curious to know if there is an optimised way. – Utsav Kesharwani Jan 24 '14 at 19:23
  • 1
    @Ajedi32 optimization is not reducing the number of lines of code, or the method chain, you need a benchmark to know sincerely which is faster (and that's optimization) – bjhaid Jan 24 '14 at 19:27
  • 1
    @theTinMan - I agree to your first comment & will definitely keep a note for the second one. After all, who doesn't want to write **good** code. – Utsav Kesharwani Jan 24 '14 at 19:28
  • @bjhaid The idea is that looping through the array twice as opposed to once would likely be bad for performance. As I said though, it's unlikely that there'll be a major difference in this case. – Ajedi32 Jan 24 '14 at 19:31

2 Answers2

2

Enumerable#each_with_object

(1..10).each_with_object([]) { |x,arr| arr.push(x*3) if x.even? }  ## evaluates to [6, 12, 18, 24, 30]
bjhaid
  • 9,114
  • 2
  • 34
  • 46
  • I'd write this using `(1..10).each_with_object([]){ |x, a| a << x*3 if x.even? } # => [6, 12, 18, 24, 30]`. I find using `< – the Tin Man Jan 24 '14 at 23:29
1

You could use a reduce:

(1..10).reduce([]){ |a,x| a << x*3 if x.even?; a }

or (equivalent, equally confusing):

(1..10).reduce([]){ |a,x| x.even? ? a << x*3 : a }
ptyx
  • 3,872
  • 1
  • 17
  • 20
  • In similar context, the following also works: `(1..10).inject([]){ |a,x| a << x*3 if x.even?; a }` But I'm not sure if this is the proper way of doing it. – Utsav Kesharwani Jan 24 '14 at 22:39
  • 1
    Agreed. Any of those work - and they might be marginally faster than select + map, but they're not easier to read and I'd rather optimize for readability than hypothetical speed. – ptyx Jan 24 '14 at 23:05
  • 1
    Look into using `each_with_object` instead of `reduce` or `inject`. `each_with_object` doesn't require the object being injected-into to be returned by the block each time, which often makes it cleaner. – the Tin Man Jan 24 '14 at 23:30