Search code examples
rubyrecursionlambdablockproc

Passing a condition as a Proc/Lambda/Block in recursive function causes it to exit once the condition inside the lambda has evaluated to true. Why?


So I want to create a recursive function that, when given an array, will return an array of elements that meet the condition passed into the function. I have tried using lambdas, blocks and procs, and every time they exit the function once that condition evaluates to true, rather than when the base case is met. I want to know why this is and how I can overcome it.

def find_by_condition_recur(arr, count, acc, &block)
  return acc if count == arr.count - 1
  puts count
  puts arr.count
  if block.call(arr[count])
    acc << arr[count]
  else
    find_by_condition_recur(arr, count += 1, acc, &block)
  end
end

EDIT:

def find_by_condition_recur(arr, findBy, count, acc)
  return acc if count == arr.count - 1
  puts count
  puts arr.count
  if findBy.call(arr[count])
    acc << arr[count]
    find_by_condition_recur(arr, findBy, count += 1, acc)
  end
end

search_condition = Proc.new { |x| x % 3 == 0 }

Solution

  • Here is some working code for you:

    def find_by_condition_recur(arr, idx=0, acc=[], &findBy)
      return acc if idx == arr.count
      if findBy.call(arr[idx])
        acc << arr[idx]
      end
      find_by_condition_recur(arr, idx + 1, acc, &findBy)
    end
    
    find_by_condition_recur([1,2,3]) { |num| num != 2 }
    # => [1,3]
    

    There were two major fixes:

    1. The base case is idx == arr.count, not idx == arr.count - 1. You only want to return here if the idx is out-of-bounds - arr.count - 1 is in bounds, so if you return in that case, you would skip the last iteration.
    2. You need to move the final find_by_condition_recur call to outside of the if findBy.call block, otherwise recursion will stop as soon as the condition fails (and the rest of the array won't be processed).

    Other than that there were a few refactorings I made:

    1. Default values for arguments. There's no reason to have to specify idx or acc arguments the first time the method is called - we know are they are going to be 0 and [] respectively so let's just set those as the default values.
    2. Block should always be the last argument, otherwise you are forcing the caller to use an ugly proc/lambda literal. It's much more idiomatic to use blocks, and you can only use blocks when it's the last argument.
    3. Using idx instead of count as the variable name, this is just more accurate.