0
\$\begingroup\$

I'm trying to recreate the method inject (Works like Reduce) from ruby Enumerable Module. Using Rubocop (A Linter) I got the error:

Cyclomatic complexity for my_inject is too high. [7/6]

The code is:

module Enumerable
  def my_inject(*args)
    list = to_a if Range
    reduce = args[0] if args[0].is_a?(Integer)
    operator = args[0].is_a?(Symbol) ? args[0] : args[1]

    if operator
      list.each { |item| reduce = reduce ? reduce.send(operator, item) : item }
      return reduce
    end
    list.each { |item| reduce = reduce ? yield(reduce, item) : item }
    reduce
  end
end

I already did my best to reduce the Cyclomatic complexity, but still have 1 above (7/6), now I stuck, any directions will be appreciate.

The code can be run on reply: https://repl.it/@ThiagoMiranda2/INJECT

\$\endgroup\$
  • 1
    \$\begingroup\$ Does my_inject() as presented work as specified? \$\endgroup\$ – greybeard Apr 2 at 8:28
  • \$\begingroup\$ Yes, works almost like the original, same behavior as describe on documentation, but some edge cases are not cover, like when we have an empty block . \$\endgroup\$ – Sevila Apr 2 at 14:29
  • \$\begingroup\$ Please can you add a description on what the code achieves, currently all I've learnt from reading your question is that it's got bad cyclomatic complexity. To be able to help people need to know what it is you're doing. \$\endgroup\$ – Peilonrayz Apr 6 at 20:49
  • \$\begingroup\$ This method works like Reduce function of any main stream language, for example if you have an array [1,2,3] and let's say you want to reduce with sum operator, so you have = 1 + 2 +3 = 6. In this case we have the array as input [1,2,3] an the return is the number 6. \$\endgroup\$ – Sevila Apr 7 at 15:07
1
\$\begingroup\$

Before I answer your question I should point out that:

list = to_a if Range

is incorrect. Range is a constant and if Range is always true. You probably means if is_a?(Range) which would mean list was undefined in most cases.

As a first attempt at solving this I would take the case where an operator is provided and have the method call itself with a block something like below.

module Enumerable
  def my_inject(*args)
    unless block_given?
      operator = args.delete_at(-1)
      initial  = args.first   
      return my_inject(initial) { |accumulator, i| accumulator.send(operator, i) }
    end

    list = is_a?(Range) ? to_a : self

    accumulator = args.first 
    list.each { |item| accumulator = accumulator ? yield(accumulator, item) : item
    accumulator
  end

end

Note: I didn't test this or check its complexity.

This code is also incorrect. There is actually a difference between an initial value that is not provided and one of nil. It is also possible for the block to to return nil which would cause the yield block not to be called for that iteration. The real solution is to check args.length not just args.first but that would increase your complexity.

To do this properly and still pass Rubocop's checks for complexity (as well as its other checks) you will need to break this method up into a few smaller methods.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Your code doesn't have problem with complexity, and now I have new paths to try. Thanks for the advice about Range, was missing that. \$\endgroup\$ – Sevila Apr 2 at 18:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.