natasha kelly natasha kelly - 1 month ago 16
Ruby Question

Collatz Chain Algorithm RUBY

I am trying to populate an array according to the Collatz sequence. The constraints for the sequence are as follows:

positive integers:

n → n/2 (n is even)

n → 3n + 1 (n is odd)

Example Output

3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1

Ideally, I wanted to construct a recursive call that would populate the array according to the constraints of the sequence. However, I believe my logic for the recursive call is extremely flawed. The intended behavior is to iterate over the nested array, manipulating only the last element of each sub array until the element reaches 1. I am trying to build my understanding of recursion and would appreciate any suggestions on how to fix this problem.

def collatzSum(maxNumber)

sequenceHash = Hash.new(0)
i = maxNumber
until i == 0 do
if i.even?
sequenceHash[i] = [(i), (i / 2)]
elsif i.odd? && i != 1
sequenceHash[i] = [(i), (3 * i + 1)]
elsif i == 1
sequenceHash[i] = [i]
end
i -= 1
end
p sequenceHash


helper_method recursion. Method should take in hash values and iterate according to if statements.

=begin
desired output
hash = {5=>[5,16, 8, 4, 2,1],
4=>[4,2,1],
3=>[3,10,5,16,8,4,2,1],
2=>[2,1],
1=>[1]}
=end


Code:

collatzChain = lambda do |k|
j = 0
k = j[-1]
until k == 1 do
if k.even?
sequenceHash[k] << (k / 2)
elsif k.odd?
sequenceHash[k] << (3 * k + 1)
end
end
j += 1
end
collatzChain.call(sequenceHash.values)
sequenceHash
end

collatzSum(5)

Answer Source

So you mention that you wanted a recursive algorithm, your current approach looks iterative to me. To be recursive, you need to call the method you're in with values closer and closer to a base condition and then, once you hit the base condition, you return back out, up the call chain building up your return values. So, for the Collatz sequence a recursive approach would look like:

def build_collatz_chain(max_number)
  return_value = [max_number]
  # our base condition is when the number passed in is equal to 1, so
  # when we get 1 as the max_number, we'll return an array looking like
  # [1]
  return return_value if max_number == 1

  if max_number.even?
    # here, with an even max_number, we'll recurse and call our method
    # again, passing in the new max_number, which is the current
    # max_number / 2.
    return_value + build_collatz_chain(max_number / 2)
  else
    # same as above, but we're odd, so we'll recurse with 3 * max_number + 1
    return_value + build_collatz_chain(3 * max_number + 1)
  end
end

and now when we call this with a value of 5, what will end up happening is something like:

call build_collatz_chain(5)
  call build_collatz_chain(16)
    call build_collatz_chain(8)
      call build_collatz_chain(4)
        call build_collatz_chain(2)
          call build_collatz_chain(1)
          We have hit the base condition! return with [1]
        return from 2 with [2, 1]
      return from 4 with [4, 2, 1]
    return from 8 with [8, 4, 2, 1]
  return from 16 with [16, 8, 4, 2, 1]
return from 5 with [5, 16, 8, 4, 2, 1]

So, now if you want a hash of all numbers up to the passed in max_number with their Collatz chains as values you can use a helper to call this for each value, up to max (this helper is iterative, but could be made recursive...exercise for the viewer if you want it recursive):

def collatz_sum(max_number)
  { }.tap do |sequence_hash|
    max_number.downto(1) do |i|
      sequence_hash[i] = build_collatz_chain(i)
    end
  end
end

and then when you call collatz_sum(5) you get back:

{5=>[5, 16, 8, 4, 2, 1], 4=>[4, 2, 1], 3=>[3, 10, 5, 16, 8, 4, 2, 1], 2=>[2, 1], 1=>[1]}

The reason your approach is iterative is in the collatzChain lambda, you are setting a value (j) and then incrementing it and just looping through until k is equal to 1. It's also an infinite loop because you initially set k as:

j = 0
k = j[-1]

and so k == 0, and then you iterate until k == 1 and then you never update what the value of k is again.