2

I am trying to understand what is a semantically right way to use map. As map can behave the same way as each, you could modify the array any way you like. But I've been told by my colleague that after map is applied, array should have the same order and the same size.

For example, that would mean using the map to return an updated array won't be the right way to use map:

array = [1,2,3,4]
array.map{|num| num unless num == 2 || num == 4}.compact

I've been using map and other Enumerator methods for ages and never thought about this too much. Would appreciate advice from experienced Ruby Developers.

Jordan Running
  • 102,619
  • 17
  • 182
  • 182
Maxim Fedotov
  • 1,349
  • 1
  • 18
  • 38
  • Well that's great, but would love to hear why @sagarpandya82 – Maxim Fedotov Jun 13 '17 at 16:02
  • 1
    While `map` iterates like `each` does, they're different tools. In actual use we see people try to use `each` when they should use `map` and they end up writing more code. Conversely, they'll use `map` instead of `each` and end up writing too much code also. `map` is for changing values, `each` is for looping over them. You can force an overlap but that's going against the flow so learn when to use each of them. – the Tin Man Jun 13 '17 at 18:09
  • 1
    When you have to use compact after a map the odds are really good you did something wrong, such as use a conditional in the block that doesn't return a value. EVERY loop of a map block should return a usable value. If you're throwing the values away with compact your code is wasting CPU time and you should have determined which values to work on using `select` or `reject` prior to passing that reduced set to `map`. – the Tin Man Jun 13 '17 at 18:19
  • 2
    To put it another way, your example *would be* the right way to use `map` iif the result you *wanted* was an array with `nil`s in place of values not equal to `2` and `4`. Since that's not the result you want (as evidenced by the need for `compact`), `map` is the wrong choice here. – Jordan Running Jun 13 '17 at 18:40

2 Answers2

4

In Computer Science, map according to Wikipedia:

In many programming languages, map is the name of a higher-order function that applies a given function to each element of a list, returning a list of results in the same order

This statement implies the returned value of map should be of the same length (because we're applying the function to each element). And the returned-elements are to be in the same order. So when you use map, this is what the reader expects.

How not to use map

arr.map {|i| arr.pop } #=> [3, 2]

This clearly betrays the intention of map since we have a different number of elements returned and they are not even in the original order of application. So don't use map like this. See "How to use ruby's value_at to get subhashes in a hash" and subsequent comments for further clarification and thanks to @meager for originally pointing this out to me.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Sagar Pandya
  • 9,323
  • 2
  • 24
  • 35
  • This is a great example of Ruby: Great flexibility but needs to be applied carefully. Thanks for the clarification. – Maxim Fedotov Jun 13 '17 at 17:53
  • @MaximFedotov thanks for the accept, but probably the wrong choice. Although somewhat useful I don't think this answer deals with your issue and example. I would even go so far as to say the example I have given is pointless. The other comments and answers deal with why `map` is the wrong choice sometimes and when not to use it. – Sagar Pandya Jun 13 '17 at 20:09
  • I think you should take more credit for your answer. While Tin Man answer is great and was interesting to see CPU and memory utilisation angle, yours was exactly on point with what I was asking about. Moreover, @meager comment on your other answer also gave some extra context. So I'd like to keep your answer as accepted. – Maxim Fedotov Jun 14 '17 at 09:23
4

Meditate on this:

array = [1,2,3,4]

array.map{|num| num unless num == 2 || num == 4} # => [1, nil, 3, nil]
    .compact # => [1, 3]

The intermediate value is an array of the same size, however it contains undesirable values, forcing the use of compact. The fallout of this is CPU time is wasted generating the nil values, then deleting them. In addition, memory is being wasted generating another array that is the same size when it shouldn't be. Imagine the CPU and memory cost in a loop that is processing thousands of elements in an array.

Instead, using the right tool cleans up the code and avoids wasting CPU or memory:

array.reject { |num| num == 2 || num == 4 } # => [1, 3]

I've been using map and other Enumerator methods for ages and never thought about this too much.

I'd recommend thinking about it. It's the little things like this that can make or break code or a system, and everything we do when programming needs to be done deliberately, avoiding all negative side-effects we can foresee.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303