Better way to do this?

One of my tables has a recurring column name / type

id, description, …, day1, day2, day3 … day7

inside each of the days is an int.

Currently, I’m using send to loop over the day columns

total = 0
7.times do |i|
total += row.send(“day” + (i+1).to_s)
end

I don’t know how (in)efficient this is. Does anyone know a better way?

Also I was reading something about {} last night and it looks like I
could write this as

total = 0
7.times do |i| { total += row.send(“day” + (i+1).to_s) }

They both are pretty legible to me, but is there a major convention I
should follow?

Also I was reading something about {} last night and it looks like I
could write this as

total = 0
7.times do |i| { total += row.send(“day” + (i+1).to_s) }

They both are pretty legible to me, but is there a major convention I
should follow?

How about:

total = 0
1.upto(7) { |i| total += row.send(“day#{i}”) }

Luke wrote:

total = 0
1.upto(7) { |i| total += row.send(“day#{i}”) }

Ah, I like that a lot better. So send is the way to go.

Does the #{i} format work with arrays as well? value[:d1] - value[:d7]
could be value[:d#{i}]?

Hi –

On Tue, 9 Jan 2007, Alex T. wrote:

7.times do |i|

They both are pretty legible to me, but is there a major convention I
should follow?

This kind of summing is often done with inject. Here’s a[n untested]
example:

total = (1…7).inject(0) {|acc,day| acc + row.send(“day#{i}”) }

The idea is that the code block is run seven times, with an
accumulator and the current day-number as arguments. The
accumulator’s value the first time through is 0. Each time through,
the block returns the sum of the accumulator and the row’s value for
the current day. That value is then used as the value for the
accumulator the next time through.

So by the seventh time, it’s returning the sum of all of them – and
that’s the final value of the whole inject expression, which then gets
assigned to total.

David


Q. What is THE Ruby book for Rails developers?
A. RUBY FOR RAILS by David A. Black (Ruby for Rails)
(See what readers are saying! http://www.rubypal.com/r4rrevs.pdf)
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)

Hi,

How about:

total = 0
1.upto(7) { |i| total += row.send(“day#{i}”) }

How about a one-liner?

total = (1…7).inject {|sum, i| sum + row.send “day#{i}”}

Docs on inject:

http://ruby-doc.org/core/classes/Enumerable.html#M003176

Cheers!
Patrick

[email protected] wrote:

could write this as

that’s the final value of the whole inject expression, which then gets
assigned to total.

Argh, beat me by a couple minutes David :slight_smile: Much better explanation
though… but isn’t inject(0) redundant? Just inject {} does the trick
because nil.to_i is 0…

Cheers!
Patrick

total = (1…7).inject {|sum, i| sum + row.send “day#{i}”}

Learn something new every day - cheers David/Patrick!

Does the #{i} format work with arrays as well? value[:d1] - value[:d7]
could be value[:d#{i}]?

Someone else might want to comment on this but as far as I know it will
only be replaced in double-quoted strings i.e. “#{var}”

You could do value[:“d#{i}”] but I’m not sure on the value of that.

I think there is a method that will convert a string to a :param but I
cant remember what its called.

Alex T. wrote:

The #{i} format works in double quoted strings and does not work with
symbols. If the keys for value were ‘d1’, ‘d2’, etc. (strings) instead
of :d1, :d2 (symbols), then you could use value["d#{i}]

Hi –

On Tue, 9 Jan 2007, Patrick R. wrote:

inside each of the days is an int.
Also I was reading something about {} last night and it looks like I
example:
So by the seventh time, it’s returning the sum of all of them – and
that’s the final value of the whole inject expression, which then gets
assigned to total.

Argh, beat me by a couple minutes David :slight_smile: Much better explanation
though… but isn’t inject(0) redundant? Just inject {} does the trick
because nil.to_i is 0…

It does default to 0, though I don’t think it’s because of nil.to_i,
since that implies that there’s an automatic to_i performed on the
accumulator, which there isn’t; you can do:

[1,2,3].inject([]) …

for example.

I usually put the 0 in anyway just because it’s sort of
self-documenting that way.

David


Q. What is THE Ruby book for Rails developers?
A. RUBY FOR RAILS by David A. Black (Ruby for Rails)
(See what readers are saying! http://www.rubypal.com/r4rrevs.pdf)
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)

Hi –

On Tue, 9 Jan 2007, Daniel H. wrote:

Does the #{i} format work with arrays as well? value[:d1] - value[:d7]
could be value[:d#{i}]?

The #{i} format works in double quoted strings and does not work with
symbols. If the keys for value were ‘d1’, ‘d2’, etc. (strings) instead
of :d1, :d2 (symbols), then you could use value["d#{i}]

To get a symbol while taking advantage of string interpolation you
could also do:

value[:“d#{i}”]

David


Q. What is THE Ruby book for Rails developers?
A. RUBY FOR RAILS by David A. Black (Ruby for Rails)
(See what readers are saying! http://www.rubypal.com/r4rrevs.pdf)
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)

Here is hopefully my last tangent for the thread.

What if I wanted to assign a value dynamically?

cell = returnEditedCell(params)

row.send(“day#{cell}”) = newVal

211: syntax error, unexpected ‘=’, expecting kEND
row.send(“day#{cell}”) = values[:“d#{cell}”].to_f

And Luke had it right, I was missing the ""s from the array call, so
value[:“d#(i)”] worked.

total = (1…7).inject(0) {|acc,day| acc + row.send(“day#{i}”) }

Just looking this over, it sort of makes sense, but could you expand on
this a little more?

(1…7).inject - number of times run, start and end values
(0) - initial value
{|acc, - the internal variable that is assigned the init value
day|} - ? not sure about is there, should it be i?
acc + … } - the code that is executed

Perhaps I am making a leap here but if these are the same Davids -

David
A. RUBY FOR RAILS by David A. Black (Ruby for Rails)

I’ve been working through your book lately. It’s been very helpful and
enlightening! Thanks for taking the time to write it.

Hi –

On Tue, 9 Jan 2007, Alex T. wrote:

total = (1…7).inject(0) {|acc,day| acc + row.send(“day#{i}”) }

Just looking this over, it sort of makes sense, but could you expand on
this a little more?

(1…7).inject - number of times run, start and end values

And it can be any Enumerable (array, range, hash, string, your own,
etc.).

(0) - initial value
{|acc, - the internal variable that is assigned the init value
day|} - ? not sure about is there, should it be i?
acc + … } - the code that is executed

Yes, you’re right: day should be i in the block variable list. I got
too cutesy with the variable names. Once again the ‘untested’
disclaimer comes to the rescue :slight_smile:

Does that answer all your questions? If not, let me know.

Perhaps I am making a leap here but if these are the same Davids -

David
A. RUBY FOR RAILS by David A. Black (Ruby for Rails)

It’s a pretty logical leap, and a correct one :slight_smile:

I’ve been working through your book lately. It’s been very helpful and
enlightening! Thanks for taking the time to write it.

Thanks – glad to have done it!

David


Q. What is THE Ruby book for Rails developers?
A. RUBY FOR RAILS by David A. Black (Ruby for Rails)
(See what readers are saying! http://www.rubypal.com/r4rrevs.pdf)
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)

not to knock the inject way being discussed (i like inject, honest!)
but there is a bit of a penalty for using an iterator versus just
adding up the respective totals. since you know you only have the 7
columns in your record so why not just total them up within the model
as an alternative? unless you plan on adding day columns to your
table, the method should never change.

class MyModel < ActiveRecord::Base
def day_total
day1 + day2 + day3 + day4 + day5 + day6 + day7
end
end

a quick test of inject vs unrolled shows (using the day1…day7 method
described previously vs. day_total method described here)

10000 iterations
inject: 0.568689000000041
unrolled: 0.119564999999983
diff: 0.449124000000058

on average, thats about a 1/2 sec difference on 10000 records. i know
that’s not a lot but it can make a difference when you have a
reporting system and everyone wants their reports at month end! :slight_smile:

anyways, thats my $0.02 on the subject.

Hi –

On Tue, 9 Jan 2007, Alex T. wrote:

Here is hopefully my last tangent for the thread.

What if I wanted to assign a value dynamically?

cell = returnEditedCell(params)

row.send(“day#{cell}”) = newVal

211: syntax error, unexpected ‘=’, expecting kEND
row.send(“day#{cell}”) = values[:“d#{cell}”].to_f

The missing ingredient here is the fact that day1=, day2=, etc. are
actually names of methods (assuming we’re still talking about those).
So you have to send the whole thing, plus any arguments, like this
(untested):

row.send(“day#{cell}=”, values[:“d#{cell}”].to_f)

I’ve sort of lost track of exactly what kind of object we’re dealing
with here, but there might be other things you can do involving
addressing attributes directly. But that’s the basic deal with the
send semantics.

David


Q. What is THE Ruby book for Rails developers?
A. RUBY FOR RAILS by David A. Black (Ruby for Rails)
(See what readers are saying! http://www.rubypal.com/r4rrevs.pdf)
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)

[email protected] wrote:

total = 0
7.times do |i| { total += row.send(“day” + (i+1).to_s) }
The idea is that the code block is run seven times, with an

for example.

I usually put the 0 in anyway just because it’s sort of
self-documenting that way.

Should have read the docs better:

enum.inject(initial) {| memo, obj | block } => obj
enum.inject {| memo, obj | block } => obj

The second form uses the first element of the collection as a the
initial value (and skips that element while iterating).

So in the following the block is only called once:

[1, 2].inject {|sum, i| “sum:#{sum} i:#{i}”} # => sum:1 i:2

This also means we need to specify the 0 in the original case or we will
never get the actual value for day 1.

Cheers!
Patrick

Hi Alex,

What if I wanted to assign a value dynamically?

cell = returnEditedCell(params)

row.send(“day#{cell}”) = newVal

not tested but it should be :

row.send(“day#{cell}=”, newVal)

or

row.write_attribute(“day#{cell}”, newVal)

or

row[ :"day#{cell} ] = newVal

211: syntax error, unexpected ‘=’, expecting kEND
row.send(“day#{cell}”) = values[:“d#{cell}”].to_f

In fact, you want to use the method day1=, day2= …
and not day1, day2.

-- Jean-François.


À la renverse.

Hi –

On Tue, 9 Jan 2007, Patrick R. wrote:

I usually put the 0 in anyway just because it’s sort of
So in the following the block is only called once:

[1, 2].inject {|sum, i| “sum:#{sum} i:#{i}”} # => sum:1 i:2

This also means we need to specify the 0 in the original case or we will
never get the actual value for day 1.

So my instincts were right, but for the wrong reason :slight_smile: Thanks for
the clarifying quote from the docs. Interesting that in most of the
inject cases one sees, the 0-default theory sort of works by
coincidence: if you’re summing an array of numbers, the first one gets
used as the basis anyway, and added to from then on; and most of the
time that you’re not using numbers, the seed value matters so you
usually see one:

[“a”,“b”,“c”].inject([]) {|arr,s| arr << s.upcase } # [“A”,“B”,“C”]
[“a”,“b”,“c”].inject {|arr,s| arr << s.upcase } # “aBC”

(Not a great real-world example, but anyway.)

David


Q. What is THE Ruby book for Rails developers?
A. RUBY FOR RAILS by David A. Black (Ruby for Rails)
(See what readers are saying! http://www.rubypal.com/r4rrevs.pdf)
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)

I would point out that the “official” way to do this is with
associations:

has_many :days

You can then do everything you wanted quite easily.

(This is actually part of normal form, which are key principles of
database design)