Question: A method for summing several variables

H

Harry Truax

------=_NextPart_000_00A3_01C48144.362462D0
Content-Type: text/plain;
charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

Hi everyone,
I am new to Ruby and have a basic question.=20

My company uses Ruby in implementing calculations for various forms, =
like a federal 1040 form for example. Each calculatable field on the =
form is represented as 'box1_1' - this means the first box on page 1 of =
the form, 'box1_2' is the second box (or field) on page 1, etc..=20

We do several calculations such as adding together several boxes as in =
this example:=20

box1_37 =3D $CUR (box1_2.to_f + box1_4.to_f + box1_6.to_f +..etc)=20

Where $CUR =3D '%0.2f'

This can be tedious, as we have some calculations that add up as many as =
one hundred boxes. We set $SAFE to 3 in our application - it needs to be =
this value.=20

We initially set all boxes to '' as in this example:=20

box1_1 =3D '' box1_2 =3D '' . . box12_1 =3D '' box12_2 =3D '' etc. . .=20

How can I add up a range of boxes without having to specifiy each box by =
name? Maybe a nice short method I could call, giving it the starting box =
and the step value (sometimes the box numbers differ by a value other =
than 1, like my example above where they differ by 2) and the number of =
boxes to add.=20

Below is a method I tried, but 'box' was not being properly handled - =
box could be say, 'box1_10', but I want the value that 'box1_10' points =
to, not the .to_f value of the string 'box1_10'.=20

def add_boxes(box, step, count)
#Init local variables.
sum =3D 0
box_num =3D box[box.rindex('_') + 1,(box.length - 1) - =
box.rindex('_')].to_i
box_prefix =3D box[0, box.rindex('_') + 1]
=20
0.upto(count) {
sum +=3D box.to_f
box_num +=3D step
box =3D box_prefix + box_num.to_s
}
end =20

Thanks a lot for your help, I have been out of the programming scene for =
a few years and any help is greatly appreciated.=20

Regards,=20

Harry Truax=20

------=_NextPart_000_00A3_01C48144.362462D0--
 
L

Lennon Day-Reynolds

Harry,

The natural way to handlie this kind of problem in Ruby would be to
store the form field values in a two-dimensional array, rather than as
specially-named variables. That way, you can use the built-in
iteration functionality that Ruby arrays support to loop through
fields, total them, etc.

I'd recommend looking at the "Containers, Blocks, and Iterators"
chapter of the Pickaxe book
(http://rubycentral.com/book/tut_containers.html) to see how this kind
of object collection is handled in "idiomatic" Ruby code.

If you need more specific help, just let us know.

Lennon
 
H

Harry Truax

Lennon,

Thanks for the quick reply. Unfortunately, I am required to use the box
variables (box1_1, box12_24,...etc.).

Harry

----- Original Message -----
From: "Lennon Day-Reynolds" <[email protected]>
To: "ruby-talk ML" <[email protected]>
Sent: Friday, August 13, 2004 3:40 PM
Subject: Re: Question: A method for summing several variables
 
H

Henrik Horneber

Hi!

I guess you meant something like this:


# $SAFE = 3
box1_1 = "2.3"
box1_2 = "2.3"
box1_3 = "3.3"
box1_4 = "5.3"
box1_5 = "6.3"
box1_6 = "-2.0"
box1_7 = "1.3"
box1_8 = "-2.3"
box1_9 = "-2.3"

boxStr = "box1_"
sum = 0
1.upto(9) do |i| boxVarStr = boxStr + i.to_s
sum += instance_eval(boxVarStr).to_f
end
puts sum



But unfortunately, this
We set $SAFE to 3 in our application - it needs to be this value.

breaks it. Unless there is a way to access local variables given their
name as a String that I don't know of (which is not unlikely at all
since I'm kinda new to this too ), you're still stuck.
But maybe this will give you a hint where to look.

Henrik
 
W

Walter Szewelanczyk

Harry said:
Lennon,

Thanks for the quick reply. Unfortunately, I am required to use the box
variables (box1_1, box12_24,...etc.).

Harry

Its not very pretty and it can be optimized quite a bit, but the concept
might work for you.

$SAFE=3

class SomeClass
attr_accessor :box1_1, :box1_2, :box1_3, :box1_4, :box1_5
attr_accessor :box2_1, :box2_2
attr_accessor :box3_1, :box3_2

def initialize
@box1_1 = 1
@box1_2 = 2
@box1_3 = ""
@box1_4 = 4
@box1_5 = ""

@box2_1 = ""
@box2_2 = ""

@box3_1 = 100
@box3_2 = 200
end

#pass in a string for the box you want
def sum(box)
find_box_variables(box).inject(0){|sum, var| sum+send(var).to_i}
end

def find_box_variables(box)
public_methods.find_all{|var_name| /^#{box}_\d+$/ =~ var_name}
end
end

s = SomeClass.new
puts s.sum("box1")
puts s.sum("box2")
puts s.sum("box3")

-------------- OUTPUTS -------------------------

7
0
300


Hope that helps,

Walt

--
Walter Szewelanczyk
IS Director
M.W. Sewall & CO. email : (e-mail address removed)
259 Front St. Phone : (207) 442-7994 x 128
Bath, ME 04530 Fax : (207) 443-6284
 
M

Martin DeMello

Harry Truax said:
Lennon,

Thanks for the quick reply. Unfortunately, I am required to use the box
variables (box1_1, box12_24,...etc.).

How about an array that mirrored the box variables? (i.e. such that
box[1][1] and box1_1 pointed to the same object)?

martin
 
J

James Britt

Yet Another Version

$SAFE = 3

# See
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/13402
# for ArrayMD
class ArrayMD < Array
def [](n)
self[n]=ArrayMD.new if super(n)==nil
super(n)
end
end

class FieldWrapper
def initialize( )
@data = ArrayMD.new( )
end

def [](box_name)
x, y = get_indices( box_name.to_s )
@data[x][y][0]
end

def method_missing( name, *args )
raise( "Unknown method #{name}") unless name.to_s =~ /^box/
unless ( name.to_s =~ /=/ )
x, y = get_indices( name.to_s )
@data[x][y][0]
else
name = name.to_s.gsub( '=', '' )
x, y = get_indices( name.to_s )
@data[x][y] = args
end

end

def get_indices( box_name )
s = box_name.gsub( "box", '')
parts = s.split( '_' )
return [parts[0].to_i, parts[1].to_i]
end
end


fw = FieldWrapper.new

fw.box1_1 = 23
p fw.box1_1

fw.send( "box1_2=", 12)
p fw[ "box1_2" ]
 
J

Joel VanderWerf

Harry said:
We initially set all boxes to '' as in this example:

box1_1 = '' box1_2 = '' . . box12_1 = '' box12_2 = '' etc. . .

How can I add up a range of boxes without having to specifiy each box
by name? Maybe a nice short method I could call, giving it the
starting box and the step value (sometimes the box numbers differ by
a value other than 1, like my example above where they differ by 2)
and the number of boxes to add.

Below is a method I tried, but 'box' was not being properly handled -
box could be say, 'box1_10', but I want the value that 'box1_10'
points to, not the .to_f value of the string 'box1_10'.

Destructive string methods are your best friends in this case: replace,
sub!, gsub!, tr!, etc. As long as you use them, you are operating on the
value that box1_10 points to. For example:

$SAFE = 3

boxes = Array.new(10) {Array.new(10)}

boxes[2][4] = box2_4 = ''

box2_4.replace "1.23"
p boxes[2][4] # ==> "1.23"

boxes[2][4].gsub!(/\d/, "#")
p box2_4 # ==> "#.##"

boxes[2][4].replace "3.45"
p box2_4 # ==> "3.45"

And now you can get fancy to make life easier:

class Table2D_StringCodedFloats # ok, choose a better name
def initialize(boxes)
@boxes = boxes
end

def [](i,j)
@boxes[j].to_f
end

def []=(i,j, val)
@boxes[j].replace val.to_s
end
end

table = Table2D_StringCodedFloats.new(boxes)

p table[2,4] # ==> 3.45
table[2,4] = 7
p table[2,4] # ==> 7.0

p box2_4 # ==> "7"
table[2,4] += 1
p box2_4 # ==> "8.0"

And now you can iterate through the boxes just by using integers in
table[i,j]:

10.times do |i|
10.times do |j|
table[i,j] = i+j
end
end

p box2_4 # ==> "6"

What the heck, let's make it Enumerable:

class Table2D_StringCodedFloats
include Enumerable

def each
@boxes.each_with_index do |row, i|
row.each_with_index do |value, j|
yield i,j,value.to_f
end
end
end
end

p table.select {|i,j,value| value > 16}
# ==> [[8, 9, 17.0], [9, 8, 17.0], [9, 9, 18.0]]
 
L

Lennon Day-Reynolds

Harry,

In that case, most of the nicer ways to do what you're looking for in
Ruby are going to unavailable -- the whole reason that container types
like arrays and hashes are in the core language is to make tasks like
this easier.

The best recommandation I'd have for you would be to define the sets
of fields you're going to be working with as arrays, which will still
give you some advantages. You'll have to manually list the fields you
want to work with, but you can quickly perform operations on those
entire lists, at least.

For example, instead of writing 'box1_X = (box1_2 + box1_4 + ...)', do
something like the following:

current_boxset = [box1_2, box1_4, <...>]
sum_of_boxset = current_boxset.inject(0.0) {|sum, current| sum += current.to_f}
box1_X = sum_of_boxset

(etc.)

This will be especially useful if you define the sets of fields you're
working with in some central place, and can maintain those datasets
seperately from the algorithmic code that operates on them; it would
even free you up to use more structured types later, so long as the
algorithms eventually got the array of strings they expected.

I'm not sure how often you need operations like 'sum' that operate
over a list of values, but you can also add a convenience method to
the Array type to clean up the uses of the code:

class Array
def sum(initial=0.0)
self.inject(initial) {|sum, current| sum += current}
end
end

...and use it like this:

my_array = [ '1.2', '3', '4.5' ]
sum = my_array.map { |val| val.to_f}.sum


Lennon
rcoder.net

P.S.: If you don't mind me asking, why the hard requirement that you
stick with the unstructured variable types? Just stuck with the legacy
code, or is there some sinister force at work?
 
J

Joel VanderWerf

Joel said:
p table.select {|i,j,value| value > 16}
# ==> [[8, 9, 17.0], [9, 8, 17.0], [9, 9, 18.0]]

and an even more relevant use of Enumerable:

p table.inject(0) {|sum, (i,j,value)| sum + value}
# ==> 900.0
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,997
Messages
2,570,240
Members
46,830
Latest member
HeleneMull

Latest Threads

Top