Search code examples
rubypass-by-referencepass-by-value

Ruby: Mutating method parameters (integer vs. array)


In the following code, my original intention was to pass the counter variable to a helper method which recursively runs, updating it along the way, and then return it when the helper function is finished running.

The counter returns zero, and I'm trying to grok why that is. I know this approach works when you're passing in an array, and I believe that is because each reference to the array points to the same object in memory, and when you use the shovel operator for example, you're mutating that object.

In this scenario, however, that isn't what's happening, and the counter variable is being reassigned to a new value in memory (wherever the resulting integer is pointing).

This is my question --> is the issue here that the counter variable is correctly being reassigned, but only within the scope which its defined? So the return 'counter +=1' does nothing to preserve the change to the counter variable, because within the scope of that function (and only within the scope of the function) the counter variable is reassigned? Having trouble thoroughly understanding this. Here is the problem statement for those interested:

Given an integer array with all positive numbers and no duplicates, find the number of possible combinations that add up to a positive integer target.

Example:

nums = [1, 2, 3]
target = 4

The possible combination ways are:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)

Note that different sequences are counted as different combinations.

Therefore the output is 7.

Here is my code:

# @param {Integer[]} nums
# @param {Integer} target
# @return {Integer}
def combination_sum4(nums, target)
    nums.sort! # n log n 
    generate_combinations(nums, target, counter = 0)
    counter
end

def generate_combinations(nums, target, counter)
    return counter += 1 if target == 0
    (1...nums.length).each do |num|
        break if num > target
        generate_combinations(nums, target - num, counter)
    end 
    counter
end 

Solution

  • This is a scope issue. The local variable counter you're passing into generate_combinations is not the same as the counter inside of it. Local variables defined in a method are scoped to that method. When you pass them to another method you are passing the value of the variable, not the variable itself. Inside of combination_sum4, counter never changes from its original value of 0.

    Solutions

    1) Use an instance variable @counter, which can be shared by these methods as long as they are defined within the same context, or on the same class or module. This would be helpful if you need to access this same value later during a subsequent method call.

    2) Don't return counter at the end of combination_sum4. Leave generate_combinations as the last line of the method. Whatever it returns will be returned by combination_sum4, too.

    def combination_sum4(nums, target)
      nums.sort! # n log n 
      generate_combinations(nums, target)
    end
    
    def generate_combinations(nums, target, counter = 0) # `counter` is now an optional argument with a default value of `0`
      return counter + 1 if target.zero?
      nums.each do |num| # use inclusive range (..) rather than exclusive range (...)
        break if num > target
        counter = generate_combinations(nums, target - num, counter) # reassign `counter` to the return value of the recursion
      end 
      counter
    end