2

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)
Prune
  • 76,765
  • 14
  • 60
  • 81
Natasha Kelly
  • 73
  • 1
  • 12
  • What is the specific problem that happens currently? – whodini9 Aug 16 '17 at 20:42
  • @whodini9 the recursive call is not running the output is stuck at {5=>[5, 16], 4=>[4, 2], 3=>[3, 10], 2=>[2, 1], 1=>[1]} and as mentioned I would like to populate the hash sub arrays until the last element equals 1 – Natasha Kelly Aug 16 '17 at 21:05
  • I also tried to implement the same logic with a different approach: def collatzSum(maxNumber) sequenceHash = Hash.new(0) # take in a number # check number againgst sequence # populate hash with numbers from the sequence check checkNum = lambda do |n| until n == 1 if n.even? (n/2) else (n*3+1) end n -= 1 end end checkNum.call(maxNumber) sequence = lambda do |i| until i == 1 do sequenceHash[i] = [checkNum.call(i)] end i -= 1 end sequence.call(maxNumber) end collatzSum(5) – Natasha Kelly Aug 16 '17 at 21:07

2 Answers2

2

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.

Simple Lime
  • 10,790
  • 2
  • 17
  • 32
1

It's not clear that a recursive operation is necessary here since this seems to be a straightforward mapping between a value x and f(x). By switching to a simple array output you can achieve what you want with:

def collatz_sum(max)
  (2..max).map do |i|
    [
      i,
      if (i.even?)
        i / 2
      else
        3 * i + 1
      end
    ]
  end.reverse + [ [ 1 ] ]
end
tadman
  • 208,517
  • 23
  • 234
  • 262