0

REVISION #3

I'm trying to develop an Ackermann in Ruby using original 3-arg model function. My attempt to do so appears to show the memoize'd version - ack taking longer than the raw version - raw - so obviously "something" is wrong:

1% ruby -v
ruby 2.0.0p648 (2015-12-16 revision 53162) [universal.x86_64-darwin16]
1% time ruby ackermann3.rb raw silent
raw 0.107u 0.023s 0:00.13 92.3% 0+0k 0+0io 0pf+0w
1% time ruby ackermann3.rb ack silent
ack 0.563u 0.049s 0:00.62 96.7% 0+0k 0+0io 0pf+0w

for this module Ackermann3.rb

##
# https://www.saylor.org/site/wp-content/uploads/2013/04/Ackermann-Function.pdf
#
# Ackermann's original three-argument function: ƒ(m,n,p) is
# defined recursively as follows for nonnegative integers m, n, and p:
#
#  ƒ(m,n,0) = m + n
#  ƒ(m,0,1) = 0
#  ƒ(m,0,2) = 1
#  ƒ(m,0,p) = m for p > 2
#  ƒ(m,n,p) = ƒ(m, ƒ(m,n - 1,p), p - 1) for n > 0 and p > 0
#
#  Usage: ruby ./ackerman3.rb raw | ack
#
# RUBY_THREAD_VM_STACK_SIZE 10M
##

fun = ARGV[0]
if fun.nil?
  puts "Usage: ruby ./ackermann3.rb {ack | raw} [silent]"
  exit 1
end

log = ARGV[1]
if log.nil?
  silent = false
elsif 'silent'.match(log.downcase)
  silent = true
else
  silent = false
end

def is_number? string
  true if Float(string) rescue false
end

def filesize(string_size)
  units = ['B', 'K', 'M', 'G', 'T', 'P', 'E']
  size = Float(string_size)

  return '0b' if size == 0
  exp = (Math.log(size) / Math.log(1024)).to_i
  exp = 6 if exp > 6 

  bits = '%.1f' % [size.to_f / 1024 ** exp]
  if "#{bits}".end_with? ".0"
    bits['.0'] = ''
  end
  '%s%s' % [bits, units[exp]]
end

stk = ENV['RUBY_THREAD_VM_STACK_SIZE']
if stk.nil?
  stk = "undef stk"
elsif is_number?(stk)
  stk = "EOF " + filesize(stk)
end

def ack(m, n, p)
  @results ||= {}
  @results[[m,n,p]] ||= begin
    if p == 0
      m + n
    elsif n == 0 && p > 2
      m
    elsif n == 0 && p > 0
      p - 1
    else
      ack(m, ack(m,n - 1,p), p - 1)
    end
  end
end

def raw(m, n, p)
  if p == 0
    m + n
  elsif n == 0 && p > 2
    m
  elsif n == 0 && p > 0
    p - 1
  else
    raw(m, raw(m,n - 1,p), p - 1)
  end
end

9.times do |m|
  9.times do |n|
    9.times do |p|

      if (m+n+p) < 9
        print "#{fun} (#{m}, #{n}, #{p}) => " unless silent
        begin
          result = Object.send(fun, m, n, p)
        rescue SystemStackError
          #yoink
          result = stk
        end
        puts "#{result}" unless silent
      end

    end
  end
end
print "#{fun} " if silent

The script is revised to address prior comments - errors I had, but I'm not seeing why the memoized variant isn't effective.

slashlos
  • 913
  • 9
  • 17
  • _Sidenote:_ `begin`/`end` in `ack` is a noop and makes no sense, just `if` does the same. Also, I do not get why do you call `ack` “memoized.” Maybe you wanted to `@ack["#{m}:#{n}:#{p}"] ||= if ...` there to actually memoize? – Aleksei Matiushkin Nov 22 '17 at 20:49
  • 2
    You never use a memoized value. So, your `ack` method does strictly more work and uses strictly more memory than the `raw` method, since it computes the exact same thing, but in addition also schlepps around a huge hash with all intermediate results that it *never looks at or uses again*. – Jörg W Mittag Nov 22 '17 at 20:52
  • Yes; it's wrong but where? the problem I think are these lines: ` @ack ||= {} @ack["#{m}:#{n}:#{p}"] = begin` which I can alter the 2nd **=** to **||=** to execute block when necessary but yes it's wrong hence my question; I think the 1st initializes a hash while second conditionally executes the block if the key isn't found? – slashlos Nov 24 '17 at 14:22

0 Answers0