views:

73

answers:

5

I'm trying to iterate through an array, @chem_species = ["H2", "S", "O4"] and multiply a constant times the amount of constants present: H = 1.01 * 2, S = 32.1 * 1 and so on. The constants are of course defined within the class, before the instance method.

The code I've constructed to do this does not function:

def fw
x = @chem_species.map { |chem| chem.scan(/[A-Z]/)}
y = @chem_species.map { |chem| chem.scan({/\d+/)}
@mm = x[0] * y[0] 
end

yields -> TypeError: can't convert Array into Integer

Any suggestions on how to better code this? Thank you for your insight in advance.

A: 

You have a syntax error in your code: Maybe it should be:

def fw
x = @chem_species.map { |chem| chem.scan(/[A-Z]/)}
y = @chem_species.map { |chem| chem.scan(/\d+/)}
@mm = x[0] * y[0] 
end
txwikinger
I think you mean "syntax error".
AShelly
@AShelly: yes. Thank you.
txwikinger
That was a translation error between my laptop and desktop. Thanks for catching that, I corrected it above however it is not the source of the error in ruby.
JZ
A: 
theIV
A: 

Here's a way to multiply the values once you have them. The * operator won't work on arrays.

x = [ 4, 5, 6 ]
y = [ 7, 8, 9 ]
res = []
x.zip(y) { |a,b| res.push(a*b) }
res.inject(0) { |sum, v| sum += v}
# sum => 122

Or, cutting out the middle man:

x = [ 4, 5, 6 ]
y = [ 7, 8, 9 ]
res = 0
x.zip(y) { |a,b| res += (a*b) }
# res => 122
Paul Rubel
+3  A: 

How about doing it all in one scan & map? The String#scan method always returns an array of the strings it matched. Look at this:

irb> "H2".scan /[A-Z]+|\d+/i
  => ["H", "2"]

So just apply that to all of your @chem_species using map:

irb> @chem_species.map! { |chem| chem.scan /[A-Z]+|\d+/i }
  => [["H", "2"], ["S"], ["O", "4"]]

OK, now map over @chem_species, converting each element symbol to the value of its constant, and each coefficient to an integer:

irb> H = 1.01
irb> S = 32.01
irb> O = 15.99
irb> @chem_species.map { |(elem, coeff)| self.class.const_get(elem) * (coeff || 1).to_i }
  => [2.02, 32.01, 63.96]

There's your molar masses!

By the way, I suggest you look up the molar masses in a single hash constant instead of multiple constants for each element. Like this:

MASSES = { :H => 1.01, :S => 32.01, :O => 15.99 }

Then that last map would go like:

@chem_species.map { |(elem, coeff)| MASSES[elem.to_sym] * (coeff || 1).to_i }
yjerem
Very nice answer.
theIV
my god you're only 18. You should think about computational science in college. That was a great answer.
JZ
A: 

(one-liners alert, off-topic alert)

you can parse the formula directly:

"H2SO4".scan(/([A-Z][a-z]*)(\d*)/)
# -> [["H", "2"], ["S", ""], ["O", "4"]]

calculate partial sums:

aw = { 'H' => 1.01, 'S' => 32.07, 'O' => 16.00 }
"H2SO4".scan(/([A-Z][a-z]*)(\d*)/).collect{|e,x| aw[e] * (x==""?1:x).to_i}
# -> [2.02, 32.07, 64.0]

total sum:

"H2SO4".scan(/([A-Z][a-z]*)(\d*)/).collect{|e,x| aw[e] * (x==""?1:x).to_i}.inject{|s,x| s+x}
# -> 98.09
mykhal