3

In Ruby is it possible to reference the array object being generated with a .map function from within that .map code block?

A very simple example would be if you were trying to only add unique elements to the returned array and wanted to check to see if that element already existed, which might look like:

array.map{ |v| v unless (reference to array being created by this map function).include?(v) }

I know that functionally this code is unnecessary because you could simply use a .uniq method on the array or push values into a separate array and check if that array already includes the value, I'm just wondering if it's possible conceptually as I've encountered a few times where such a reference would be useful. Thanks.

1
  • reduce, select, reject, each_with_object are more ideal for this kind of problem, if you show a use case it would be easier to identify which to you Commented May 26, 2014 at 0:01

3 Answers 3

1

Not so far as I know in map. You could however use reduce or inject to reference the collection. So:

array.reduce([]) {|memo, v| memo << v unless memo.include? v; memo }

Or...

array.inject([]) do |memo, v| 
    memo << v unless memo.include? v
    memo
end

To clarify some of the questions about reduce, the final return value for reduce become memo, but if you return the v instead of memo, v becomes the memo, which first pass for an array like [1,2,3,2,1] would be 1. So then your aggregated result gets dropped. So you need to add it to the aggregate and return the aggregate.

Of note: I agree with most commenters, uniq is the better way to go, both as it seems a more clear statement of intent, but also from a performance standpoint. See this gist for details.

Sign up to request clarification or add additional context in comments.

12 Comments

collect is an alias of map. If you cannot do it with the latter, then you cannot do it with the former either.
Thanks. I forgot about that. I think collect used to alias to reduce? Maybe I'm forgetting why I associated the two. Updated to remove reference to collect.
Yeah, so the final return value does become memo, but if you return the v instead of memo, v becomes the memo, which first pass for an array like [1,2,3,2,1] would be 1. So then your aggregated result gets dropped. So you need to add it to the aggregate and return the aggregate.
Okay, so the problem occurs when the condition is not met and so the return value of that iteration (and thus the accumulator during the next iteration) is no longer the array. Thanks for taking the time to explain that, I really appreciate it.
@user2017331 Thanks. I tend to use inject if I'm doing some sort of feed-back and each_with_object if the block's return value doesn't (or shouldn't) matter. The name each_with_object also matches the block's argument order which is handy.
|
1

#inject is the most common answer, but you could also consider #each_with_object

[1,1,3,4].each_with_object([]) {|v, o| o.push(v) unless o.include?(v) }
=> [1, 3, 4]

Comments

0

I think instead if:

array.map{ |v| v unless (reference to array being created by this map function).include?(v) }

you should just write:

array.uniq

1 Comment

Thanks spickermann. I did address that in my question, it was more of a conceptual question and I was using that as a very simple example to help explain what I was asking.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.