Re: [SPOILER] Ruby RPNCalc (Solution to Quiz #25)

Mike Stok <[email protected]> Fri, 1 Oct 2004 22:01:18 -0400 (EDT)
Newsgroups gmane.comp.lang.perl.qotw.discuss
Message-ID <[email protected]>
On Fri, 1 Oct 2004, James Edward Gray II wrote:

> I better start with a big thanks to the quiz writer, because I thought 
> this one was a blast.  Especially for those of us that have found 
> memories of our HP calculators.
> 
> Below is my solution in Ruby.  I tried to be pretty liberal with my 
> comments, to make it easier to follow, even if you aren't a Ruby 
> person.  Unfortunately, I think it requires Ruby 1.8.x, which many 
> people would probably need to install.  Sorry about that.
> 
> I believe my solution covers everything in the quiz and then some, but 
> I better qualify a few points...

Here's my quick ruby RP calculator program & module, the interface to the
calculator object is simple, you enter one or more numbers, and do one or
more operations.  In action:

[mike@ratdog 025]$ ./rpcalc
> 10101 0x10101 0b10101 010101
0:[4161]
1:[21]
2:[65793]
3:[10101]
> **
0:[568179274422623438952525153785800548536498157874977327044688898089602930883890155950164274210872183445236961783406805550438322777586001712779851306998072988080327268714418874307383988352213907058951896803013978203519590845139405127235060300306738246373902109123881982857353181559005823461815348572678275728637884312298022134693068498166756871918006257214730951033702927783123911852184715207346238474341440592866380170782308154490427309487955042797263178532440550307862212637768141671021284306484191330642412551228550609294204074872915756188435946841350784829229055114581876928779818855515251555394693392643133209189672896721433274665114251503684774689535091670902536628774741888397780688615228976094478226394100362753820633761255621434983907452655178775614247608444886720894414297893788773308559959336705618127984122939908545286085788249368593985476894879640880185014464776513011568864827263572191661939108637649583001451947975447305167324438999670512821496747174926546399507073297040563203204052372400196881043581583781064972871334363018791272081820382549037235300485872591635812121415227073315421558826409074429599356214961891128758769729647264253269216235491698339052937369606724869297677022397314418651333259408126967422208346703502793656838055452783170760043450692562609690086418039516345370631745378358175393695276091158652464472686970120622601519823053803177778029682393263327023057803959919540237010126075944167779077227191865628897354644423099739841059686839076931660063939142429721592338994057999265701506725661520875743233201165814582855122256545095323382485055233815850045399645161441148833136077212809392371551211521080573031128409934665674142718422272556899651681072576266937169718492503842661034590948092405588394074810000573225076384207458347235457468566920881820391232998636242795759743403295558686065055833532305868463396024127379095401378574881975183512057475108626776792759866845765589478198657644496364553759508568046754559232564594217792240280986105475950168843949966168259547742721616976329172163850275827773157334901441432657008208849906023018619420168044616132792365746633034264018365876108551152934131165096827697347745164818558516407891470138896664312723313018485999661686210419343961177034048747755977895452658012415304155789177332944132895674395956506002095342789983592438181614103056172402235524845026734381177326146506665566714945330764551628928384556476950955409022129463082379862997370895016997157240533895436194502250871577037991299773138121532388322708037065728002477667857546701602753030563225452202187664067667963701137544552665900719814823225351412073748056787321007825567469760031802333137323788803434709231571912743158048093703598752262906312431955233860960890385244097014892289827685806631759914661314375069386124662763599786005860725780476750512545673230326772220189663248693721352655382712925808940537338124059414049570286221227016213966724317027803321529475066153708984838536384197350576914206923160668876517632142292888410505270208608259374245507070041627458591937847323636674655757690750081561091253093633448696019006376847395746529995154016151906812149216457221991370896717872383012149498834943980848253789148057849710090359039734067413345671635433438226123655414842341594761635909545519736748325528984816208426242742499016816463876523477513880965698320195748470106818750215672263802134255846023971413353691395586042662356431580180732082519930343312629875756416433013939413395859269045757903344046817731720315504839763135393246759681649508844350999359074000451136406848345578370665703512013045362509576892110509314705049776264600647256793298660700205219429361925321342571818970700146761577727240923087270542079434164704837923909304072427652370350044159764193989546758615694096411598838589416773223885439766376919600100939781977933979401260541768696134049099352165240014019676391068804950019220776034474117343644182784237200871562164325491516934523415203117808871426259237302546042217908622961564365389722112253235116090335607767811563109215996355078914812968752812757655035218739605764030642949691830915125880615752952739466587665928229317042461692787513730252745354203321459333802629441191306512586432268127393524292531799762498207028236871785307489534274949880190721649585085754634917713849697338350938779124333743209752786388553938724260267946185911464730745996418457422069882037602626750158417928510882586480011171642550621474603437267111027413492635044279760976709537714968834114645373422483224466032037093824473627400939850006835623051800864937952313570684635901603596219743231753783877901362475328509754772461392576971334722740416176061344837835713489347310199712303156487440385912291064878196761019756050373043483740281333909211725389554364026863492229115526791769819849817766006075589010683833892513329900461770361520184282655508106694105593621947851764631524638743891736307982980091624134983713446040046100822580399359059439832140564549753691512071471468416610338573714873903166214809853534435395651147130936657775052668422393110669837893075562919781732196219537030488241754877915214275405657841825467332459709367948472889379146989023988189535312246660322106036199268032127833115783281499995488928858410404673206086750047970092107362404218033342377520122156533671628415968139821491370124772320025440648682640316674668347741442110232770626591816620509829940396776760775531567470137897028395469025755212621063132863157439483220539083774623976953526973769514551907600573130892962853779485579450757632652238230299574518736721116001116082473758355221]
1:[65793]
2:[10101]
> drop swap hex
0:[2775]
1:[10101]

etc.

I think it needs ruby 1.8.2 (recent snapshot) but I am sleep deprived at 
the moment so could be wrong!  A slightly different approach, I'm just 
re-igniting my Ruby spark, so there is much which could be improved.  Hope 
you like it.

Thanks to the guys who take the time to write the quizzes & summarise.

Mike

Program:

#!/usr/bin/env ruby
 
require 'rpcalc'
 
calc = RpCalc.new
 
loop do
    print '> '
    break unless line = gets
    line.split(' ').each do |item|
        if num = (Integer(item) rescue Float(item) rescue nil)
            calc.enter(num)
        else
            begin
                calc.do(item)
            rescue
                puts "oops"
                break           # ditch rest of line
            end
        end
    end
    puts calc
end

and the module (+ unit tests :-)

class RpCalc
    # class implementing a reverse polish calculator for perl
    # quiz of the week 25

    # quick & easy way to deal with the 2 / 3 = 0 problem :-)

    require 'mathn'

    # @@procs contains procs for anything whose effect on the 
    # stack is predictable in terms of number of arguments required
    # and number of results returned.
    #
    # "Irregular" operations are handled in do()

    @@procs = Hash.new

    # binary ops which Ruby can handle for me

    %w{ + - * / % ** }.each do |op|
        @@procs[op] = proc { |a, b| b.send(op, a) }
    end

    # other operations

    @@procs['swap'] = proc { |a, b| [b, a] }
    @@procs['drop'] = proc { |a| [] }

    # n.b. can't dup Fixnums, hence rescue...
    @@procs['dup']  = proc { |a| [a, (a.dup rescue a)] }

    # hmmm, why do I need the 0 + here...  ruby 1.8.2 snapshot
    # may be to blame, and there must be a sexier way to do this.
    @@procs['sin']  = proc { |a| 0 + Math.sin(a) }

    @@procs['neg']  = proc { |a| -a }

    def initialize
        do_clear
        do_dec
    end

    # Enter value(s) onto the stack

    def enter(*values)
        values.each do |value|
            @stack.unshift(value)
        end
    end

    # Return contents of the stack as "normal" ruby types
    #
    # This hides mathn Rational results from the outside world.
    # Don't know if it's necessary or not.

    def to_a
        @stack.collect { |n| n.integer? ? n.to_i : n.to_f }
    end

    def to_s
        print = []
        self.to_a.each_with_index do |val, i|
            print << "#{i}:[#{val.integer? ? val.to_s(@base) : val}]"
        end
        print.join("\n")
    end

    # Perform an operation

    def do(*ops)
        ops.each do |op|
            # Checks for the operation in @@procs, if operation not found there
            # then see if we have a do_<operation> we can use before failing

            if p = @@procs[op]
                n_args = p.arity
                raise RuntimeError, "stack empty"  if @stack.size < n_args
                args = @stack.shift(n_args)
                @stack.unshift(*p[*args])
            else
                self.send("do_#{op}") rescue raise RuntimeError, "bad command"
            end
        end
    end

    protected

    # clear the stack 

    def do_clear; @stack = []; end

    # base setters 

    def do_bin; @base = 2; end
    def do_oct; @base = 8; end
    def do_dec; @base = 10; end
    def do_hex; @base = 16; end

end

if __FILE__ == $0
    require 'test/unit'

    class TC_Spec < Test::Unit::TestCase

        # test the things described in the problem description

        def test_simple
            r = RpCalc.new
            assert_equal([], r.to_a, 'initially empty')
            r.enter(3)
            assert_equal([3], r.to_a, 'enter 3')
            r.enter(5)
            assert_equal([5, 3], r.to_a, 'enter 5')
            r.do('+')
            assert_equal([8], r.to_a, 'add')
            r.enter(2)
            r.do('*')
            assert_equal([16], r.to_a, 'multiply')
            r.enter(2, 3)
            r.do('+', '/')
            assert_equal([3.2], r.to_a, 'add and divide')
            assert_raises(RuntimeError, 'too few stack entries') { r.do('/') }
        end

        # test extra operators

        def test_ops
            r = RpCalc.new
            r.enter(4, 2)
            r.do('-')
            assert_equal([2], r.to_a, '-')
            r.do('clear')
            r.enter(123, 45)
            r.do('%')
            assert_equal([33], r.to_a, '%')
            r.do('clear')
            r.enter(2, 16)
            r.do('**')
            assert_equal([65536], r.to_a, '**')
        end

        # test commands

        def test_commands
            r = RpCalc.new
            r.do('clear')
            assert_equal([], r.to_a, 'clear empty stack')
            r.enter(1, 2, 3)
            r.do('clear')
            assert_equal([], r.to_a, 'clear stack with contents')

            r.enter(1, 2, 3)
            assert_equal([3, 2, 1], r.to_a, 'enter order (to remind me)')
            r.do('swap')
            assert_equal([2, 3, 1], r.to_a, 'swap')
            r.do('clear')
            r.enter(1)
            assert_raises(RuntimeError, 'swap too few values') { r.do('swap') }

            r.do('clear')
            r.enter(1, 2, 3)
            r.do('drop')
            assert_equal([2, 1], r.to_a, 'drop')
            r.do('clear')
            assert_raises(RuntimeError, 'drop empty stack') { r.do('drop') }

            r.do('clear')
            assert_raises(RuntimeError, 'dup empty stack') { r.do('dup') }
            r.enter(1, 2, 3)
            r.do('dup')
            assert_equal([3, 3, 2, 1], r.to_a, 'dup')

            assert_raises(RuntimeError, 'bad command test') { r.do(nil) }
        end

        # try adding sin

        def test_misc
            r = RpCalc.new
            r.enter(3)
            r.do('sin')
            assert_equal([Math.sin(3)], r.to_a, 'sin')
            r.do('neg')
            assert_equal([-Math.sin(3)], r.to_a, 'neg')
        end

        # utility

        def test_utility
            r = RpCalc.new
            assert_equal('', r.to_s, 'empty to string')
            r.enter(9)
            assert_equal('0:[9]', r.to_s, 'one entry to string')
            r.do('bin')
            assert_equal('0:[1001]', r.to_s, 'one entry to binary string')
            r.do('oct')
            assert_equal('0:[11]', r.to_s, 'one entry to octal string')
            r.do('hex')
            r.enter(17)
            assert_equal("0:[11]\n1:[9]", r.to_s, 'two entries to hex string')
        end

    end
end

-- 
[email protected]                    |           The "`Stok' disclaimers" apply.
http://www.stok.co.uk/~mike/       | GPG PGP Key      1024D/059913DA 
                                   | Fingerprint      0570 71CD 6790 7C28 3D60
                                   |                  75D2 9EC4 C1C0 0599 13DA