user3071121
user3071121

Reputation: 643

Why Ruby .map doesn't work with if statement

I'm getting an unexpected result with using if inside .map:

a = [1,2,3,4,5]
a.map do |item|
  item + 10 if item < 4
  item + 9  if item > 4
end

I expected: [11,12,13,13,14]
...but I'm instead getting: [nil, nil, nil, nil, 14]

Why's that?

Secondly, I know that the last expression is the return value of method So does this true with if statement? The last expression is the return value of if statement.

Thanks!

Upvotes: 0

Views: 9579

Answers (4)

SRack
SRack

Reputation: 12203

Ruby 2.7+

There be a solution now!

Ruby 2.7 is introducing filter_map for this exact purpose. It's idiomatic and performant, and I'd expect it to become the norm very soon.

For example:

numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]

Here's a good read on the subject.

Hope that's useful to someone!

Upvotes: 2

Uri Agassi
Uri Agassi

Reputation: 37409

We can demonstrate the problem you have by writing the block as a method:

def test_map(item)
  item + 10 if item < 4
  item + 9  if item > 4
end

test_map 3
# => nil
test_map 5
# => 14

What happens here? For item=3 the first line returns 13, but that's not what's returned from the method - the method continues to the next line, which is evaluated to nil...

In order to return a single value according to several conditions, you can use if..elsif..else construct, or a case..when construct:

def test_map2(item)
  case item
    when 0..4
      item + 10
    when 4..10
      item + 9
    else
      item
  end
end

test_map2 3
# => 13
test_map2 5
# => 14

case..when returns the block after the first when clause which is evaluated to true.

Upvotes: 2

Marek Lipka
Marek Lipka

Reputation: 51161

It's because if you use map, you create array containing values evaluated from block passed into map method. So in this case, the last value evaluated in first 4 elements is item + 9 if item > 4, which returns nil.

Upvotes: 0

Frank Shearar
Frank Shearar

Reputation: 17132

For elements that are < 4, item + 10 if item < 4 does indeed return the expected value. But then the second statement executes, and map returns that value. For item < 4, item + 9 if item > 4 returns nil.

Your map should look like this instead:

a.map do |item|
  if item < 4 then
      item + 10
  else
      item + 9
  end
end

What do you do if item == 4?

Upvotes: 2

Related Questions