Reputation: 530
This code takes an array and returns only unique values.
Why is the second 'keep' required for this code to work?? Without it, I get this error:
NoMethodError: undefined method `include?' for nil:NilClass
class Array
def my_uniq_inject
self.inject([]) do |keep, num|
keep << num unless keep.include?(num)
keep # why is this required?
end
end
end
Upvotes: 0
Views: 156
Reputation: 27207
inject
feeds the output of the block back in at each stage, and returns the output of the block at the end.
When you are using inject
to aggregate data, you need to return the object that is receiving the combined data at the end the block, otherwise on the next iteration the block variable will end up pointing at something else, causing odd effects - usually an error.
You don't have to use inject
purely for aggregation though, and could in theory switch objects around if it suited your purpose. In practice I think this is a rare use of inject
Upvotes: 0
Reputation: 128317
You may be confused because normally arr << x
returns arr
, so you would think you're fine.
It's the unless
part that can screw things up here. If the last element in the array is not unique (i.e., it's already appeared earlier in the array) then the unless
clause will cause the expression to evaluate to nil
.
See for yourself:
arr = []
arr << 1 # [1]
arr << 2 unless false # [1, 2]
arr << 3 unless true # nil
Upvotes: 5
Reputation: 7561
The second keep
is required because of your condition in the previous line. If keep.include?(num)
evaluates to false, nil
will be returned to the inject
accumulator. That's not really what you want to happen: you basically want that iteration to be skipped, but retain the previous array. The second keep
allows you to pass the array back to the accumulator.
Upvotes: 0
Reputation: 258138
You need keep
at the end of the block, because the result of the block is used as the accumulator for the next iteration of inject
.
Without that keep
, the first line in your block will sometimes return keep
but sometimes return nil
(when the conditional is not met, specifically).
Upvotes: 1
Reputation: 230286
Because inject
/reduce
takes return value of your block and replaces memo/accumulator with it.
You could use each_with_object
which doesn't replace the memo
self.each_with_object([]) do |num, keep|
keep << num unless keep.include?(num)
end
Upvotes: 2
Reputation: 118261
The second keep
is for to reinitialize the keep
here do |keep,num|
,after the first pass. See the doc enum#inject
is saying at the second para last line At the end of the iteration, the final value of memo is the return value for the method.
Upvotes: 1