Cumulative weighted average in ruby - ruby

I am trying to implement a cumulative weighted average function that takes as argument the
list
[[1000, 3.1], [500, 1.2], [800, 7.1], [1300, 8.88]]
and returns (rounded to 2 decimal places here)
[3.1, 2.47, 4.08, 5.81]
For, example: 2.47 = (1000 * 3.1 + 500 * 1.2) / 1500.
I have currently solved this using the following piece of code:
def cumulative_weighted_average(list)
cs = 0
qu = 0
res = list.inject([0]) do |s, el|
cs += el[0] * el[1]
qu += el[0]
s + [cs.to_f / qu]
end
res.shift
res
end
Is there a shorter (more compact) way of doing this?
Edit:
Thanks for the answers below! The list will on average contain about 1000 entries, so not sure about the speed requirement. Since I need to be able to essentially track two values within the block, is there some extension of inject that allows you to write
list.inject([0,0]){ |s1, s2, el| ...}
where s1 and s2 are initialized to 0?

I think this is what you want:
def cumulative_weighted_average list
cs, qu = 0.0, 0.0
list
.map{|x, w| [cs += x * w, qu += x]}
.map{|cs, qu| cs / qu}
end
cumulative_weighted_average([[1000, 3.1], [500, 1.2], [800, 7.1], [1300, 8.88]])
# => [3.1, 2.466666666666667, 4.078260869565217, 5.812222222222222]
For the additional question, things like this are possible:
list.inject([0,0]){|(s1, s2), el| ...}

Is there a shorted (more compact) way of doing this?
I can try for you..
arr = [[1000, 3.1], [500, 1.2], [800, 7.1], [1300, 8.88]]
arr2 = (1..arr.size).map do |i|
b = arr.take(i)
b.reduce(0){|sum,a| sum + a.reduce(:*)}/b.reduce(0){|sum,k| sum + k[0]}
end
arr2
# => [3.1, 2.466666666666667, 4.078260869565217, 5.812222222222222]

You can avoid the "outer" temporary variables, and make things look a little cleaner, and idiomatic Ruby, if you allow for a two-stage calculation (which is not necessarily slower, same amount of maths is involved):
def cumulative_weighted_average list
cumulative_totals = list.inject( [] ) do |cumulative,item|
tot_count, tot_sum = cumulative.last || [0, 0.0]
next_count, next_value = item
cumulative << [ tot_count + next_count, tot_sum + next_count * next_value ]
end
cumulative_totals.map { |count,sum| sum/count }
end
p cumulative_weighted_average(
[[1000, 3.1], [500, 1.2], [800, 7.1], [1300, 8.88]] )
=> [3.1, 2.46666666666667, 4.07826086956522, 5.81222222222222]

Related

The Number of the Smallest Unoccupied Chair solution in ruby

I am learning ruby and have started practicing problems from leetcode, yesterday I have a problem which I am not able to solve since yesterday.
I tried hard doing that in ruby, but not able to do yet.
I tried this
def give_chair(a)
u = a.uniq
d = []
u.each do |i|
d << i if a.count(i) == 1
end
d
end
def smallest_chair(times, target_friend)
friend = times[target_friend]
sorted_arrival_times = times.sort
leave_time_chair = {}
chair = 0
chairs_array = []
uniq_chars_array = []
sorted_arrival_times.each do |i|
if leave_time_chair.keys.select { |k| i[0] > k }.empty?
leave_time_chair[i[1]] = chair
chair+=1
else
all_keys = leave_time_chair.keys.select { |k| k <= i[0] }
chairs_array = leave_time_chair.values
p chairs_array
if give_chair(chairs_array).empty?
leave_time_chair[i[1]] = chairs_array.sort.first
else
leave_time_chair[i[1]] = give_chair(chairs_array).sort.first
end
end
if i == friend
p leave_time_chair
return leave_time_chair[i[1]]
end
end
end
# a = [[33889,98676],[80071,89737],[44118,52565],[52992,84310],[78492,88209],[21695,67063],[84622,95452],[98048,98856],[98411,99433],[55333,56548],[65375,88566],[55011,62821],[48548,48656],[87396,94825],[55273,81868],[75629,91467]]
# b = 6
# p smallest_chair(a, b)
but it is failing for some test cases.
I am not able to create an algorithm for it.
Question = https://leetcode.com/problems/the-number-of-the-smallest-unoccupied-chair
My approach:
First I sort the times array according to arrival times.
Then I iterate over each array element
Now if the arrival time is greater than all the previous leaving time (I am creating key, value pair of leaving time and chair given) then I add a new key=> value pair in leave_time_chair (which is hash) and where key is the leaving time of current array and value is the chair given to it.
Then I increment the chair (chair+=1)
Else I get all those leaving time which are equal or less than the current arrival time (all_keys = leave_time_chair.keys.select { |k| k <= i[0] })
Then I get all the chairs of those times
Now I have all the chairs like this => [0, 0, 1, 2] so I wrote one function [ give_chair(a) ] which gives me those elements which are not repeated. like this => [1, 2] and then I assign the shortest number (chair) to the leaving time of current array. and so on...
Then if my current array is equal to the friend I return the chair of it. by extracting it from a hash (leave_time_chair) return leave_time_chair[i[1]]
my naive solution (not optimize yet), basically my idea that i flat-map the input array into an array with each element is a pair [time arrive/leave, friend index], then i will sort that array base on time (don't care arrive or leave), if both pair have same time, then i'll compare the arrive time of fiend index. Finally i loop through the sorted array and evaluate minimum free chair index each step, whenever i meet the targetFriend i return that minimum free chair index.
# #param {Integer[][]} times
# #param {Integer} target_friend
# #return {Integer}
def smallest_chair(times, target_friend)
# times = [[1,2],[4,7],[2,4]]
# targetFriend = 1
sit_times = times.each_with_index.inject([]) { |combi, (time, index)|
combi += [[time.first, index], [time.last, index]]
}
# [[1, 0], [2, 0], [4, 1], [7, 1], [2, 2], [4, 2]]
sit_times.sort! {|x, y|
c = x[0] <=> y[0]
# [[1, 0], [2, 0], [2, 2], [4, 1], [4, 2], [7, 1]]
c = times[x[1]][0] <=> times[y[1]][0] if c == 0
# [[1, 0], [2, 0], [2, 2], [4, 2], [4, 1], [7, 1]]
c
}
chairs = {} # to mark time of friend
occupied = Array.new(times.size, 0) # occupied chair: 1, otherwise: 0
min_free = 0 # current minimum not occupied chair
sit_times.each do |time, friend_index|
if target_friend == friend_index # check
return min_free
end
sit = chairs[friend_index]
if sit # leave
occupied[sit] = 0
chairs[friend_index] = nil
min_free = sit if min_free > sit
else # arrive
chairs[friend_index] = min_free
occupied[min_free] = 1
min_free += 1 until occupied[min_free] == 0 # re-calculate
end
end
end
Note: the code pass test cases on leetcode but the performance is not good.
update
here is the better version, using 3 priority queues, one for arrive times, one for leave times and the last for chair.
PriorityQueue class
class PriorityQueue
attr_reader :length
def initialize(opts={}, &comparator)
order_opt = opts.fetch(:order, :asc)
#order = order_opt == :asc ? -1 : 1
#comparator = comparator
#items = [nil]
#length = 0
end
def push(item)
#items << item
#length += 1
swim(#length)
true
end
def pop
return nil if empty?
swap(1, #length) if #length > 1
#length -= 1
sink(1) if #length > 0
#items.pop
end
def empty?
#length == 0
end
def swap(i, j)
temp = #items[i]
#items[i] = #items[j]
#items[j] = temp
end
def in_order?(i, j)
x = #items[i]
y = #items[j]
order = #comparator.nil? ? (x <=> y) : #comparator.call(x, y)
order == #order
end
def swim(from)
while (up = from / 2) >= 1
break if in_order?(up, from)
swap(up, from)
from = up
end
end
def sink(from)
while (down = from * 2) <= #length
down += 1 if down < #length && in_order?(down + 1, down)
break if in_order?(from, down)
swap(down, from)
from = down
end
end
end
smallest_chair with priority queues (note that i found using sort is faster than a queue for arrive times, but basically the idea is same)
def smallest_chair_pq(times, target_friend)
# a_pq = PriorityQueue.new { |x, y|
# x[0] <=> y[0]
# }
#
# times.each do |t|
# a_pq.push(t)
# end
# sort arrive times is faster than a priority queue
a_pq = times.sort_by(&:first).reverse
# leave times queue
l_pq = PriorityQueue.new { |x, y|
c = x[0] <=> y[0]
c = x[1] <=> y[1] if c == 0
c
}
# chair-indexes queue
# consider case a friend come in at arrive-time at1
# and there's a range chairs with leave times in range lm <= at1 <= ln
# that mean that friend could pick one of those chairs
# and according this problem requirement, should pick the minimun chair index
c_pq = PriorityQueue.new
target_time = times[target_friend][0]
last_chair_index = 0
until a_pq.empty?
a_top = a_pq.pop
arrive_time = a_top.first
if l_pq.empty?
return 0 if arrive_time == target_time
l_pq.push([a_top.last, 0])
else
l_top = l_pq.pop
if l_top.first <= arrive_time
c_pq.push(l_top.last)
until (l_ntop = l_pq.pop).nil? || arrive_time < l_ntop.first
c_pq.push(l_ntop.last)
end
l_pq.push(l_ntop) unless l_ntop.nil?
min_chair_index = c_pq.pop
return min_chair_index if arrive_time == target_time
l_pq.push([a_top.last, min_chair_index])
else
unless c_pq.empty?
chair_index = c_pq.pop
return chair_index if arrive_time == target_time
l_pq.push([a_top.last, chair_index])
else
last_chair_index += 1
return last_chair_index if arrive_time == target_time
l_pq.push([a_top.last, last_chair_index])
end
l_pq.push(l_top)
end
end
end
end

How can I remove the first 3 duplicate values and return an array with the remaining values?

Given these arrays, how do I remove three occurrences of a value while keeping the fourth or fifth in the array?
[1,5,1,1,1] # => [1,5]
[3,3,3,2,3] # => [3,2]
[3,4,5,3,3] # => [4,5]
[1,1,1,1,1] # => [1,1]
[1,2,2,4,5] # => [1,2,2,4,5]
Here's what I've tried:
array = [1,5,1,1,1]
top3 = array.select { |x| array.count(x) >= 3 }[0..2]
last2 = array - top3
This strategy (and similar) only seem to work when there are three duplicates but not four or five. Are there elegant solutions to this problem?
UPDATE: Thank you for your amazing answers. As a beginning rubyist I learned a lot just from analyzing each response. My question came from a Ruby Koan challenge for a dice program. Here's my complete solution implemented with Abdo's suggestion. I'm sure there are more efficient ways to implement the program :)
def score(dice)
a,b,c,d,e = dice
array = [a,b,c,d,e]
total = 0
triples = array.select {|x| array.count(x) >= 3}[0..2]
singles = array.group_by{|i| i}.values.map{ |a|
a.length > 2 ? a[0, a.length - 3] : a
}.inject([], :+)
# Calculate values for triples
# 1 * 3 = 1000pts
# 2 * 3 = 200pts
# 3 * 3 = 300pts
# 4 * 3 = 400pts
# 5 * 3 = 500pts
# 6 * 3 = 600pts
case triples[0]
when 1 then total += triples[0]*1000
when (2..6) then total += triples[0]*100
end
# Calculate values for singles:
# 1s = 100pts each
# 5s = 50pts each
singles.include? (1) ? singles.select {|x| x == 1 }.each {|x| total += x*100 } : total
singles.include? (5) ? singles.select {|x| x == 5 }.each {|x| total += x*10 } : total
return total
end
puts score([5,1,1, 5, 6]) # 300 points
puts score([]) # 0 points
puts score([1,1,1,5,1]) # 1150 points
puts score([2,3,4,6,2]) # 0 points
puts score([3,4,5,3,3]) # 350 points
puts score([1,5,1,2,4]) # 250 points
array = [1,5,1,1,1]
occurrence = {}
array.select do|a|
if(array.count(a) > 3)
occurrence[a] ||= []
occurrence[a] << a
occurrence[a].count > 3
else
true
end
end
PS: This solution preserves the order of the elements in the original array
Here's a faster solution when the size of the array is large:
(I avoid using count because it would loop through the array in an inner loop)
arr.inject({}) {
|h, i| h[i] ||= 0; h[i] += 1; h
}.collect_concat {|k,v| [k] * (v > 2 ? v - 3 : v) }
Here's the fruity comparison to the other working solutions:
arr = 1000.times.collect { rand(100) }.shuffle
require 'fruity'
compare do
vimsha {
occurrence = {};
arr.select do|a|
if(arr.count(a) > 3)
occurrence[a] ||= []
occurrence[a] << a
occurrence[a].count > 3
else
true
end
end
}
caryswoveland {
arr.uniq.reduce([]) {|a,e| a + [e]*((cnt=arr.count(e)) > 2 ? cnt-3 : cnt)}
}
aruprakshit {
num_to_del = arr.find { |e| arr.count(e) >= 3 }
if !num_to_del.nil?
3.times do
ind = arr.index { |e| e == num_to_del }
arr.delete_at(ind)
end
end
arr
}
# edited as suggested by #CarySwoveland
abdo {
arr.each_with_object(Hash.new {|h,k| h[k]=[]}) {|i,h| h[i] += 1
}.collect_concat { |k,v| [k] * (v > 2 ? v - 3 : v) }
}
broisatse {
arr.group_by{|i| i}.values.map{ |a|
a.length > 2 ? a[0, a.length - 3] : a
}.inject([], :+)
}
end
Here's the comparison result:
Running each test 64 times. Test will take about 48 seconds.
broisatse is faster than abdo by 30.000000000000004% ± 10.0%
abdo is faster than aruprakshit by 4x ± 1.0 (results differ: ...)
aruprakshit is similar to caryswoveland (results differ: ...)
caryswoveland is similar to vimsha (results differ: ...)
Note: I took #aruprakshit's code outside the method so we don't waste time in the method call itself.
When the array's size is increased further:
arr = 1000.times.collect { rand(1000) }.shuffle
we get:
abdo is faster than broisatse by 3x ± 1.0
broisatse is faster than aruprakshit by 6x ± 10.0
aruprakshit is faster than caryswoveland by 2x ± 1.0
caryswoveland is similar to vimsha
Another way, assuming order need not be preserved (which is consistent with a comment by the asker):
array = [1,2,4,1,2,1,2,1,1,4]
array.uniq.reduce([]) {|a,e| a + [e]*((cnt=array.count(e)) > 2 ? cnt-3 : cnt)}
#=> [1, 1, 4, 4]
Try something like:
a.group_by{|i| i}.values.map{|a| a[0, a.length % 3]}.inject([], :+)
This will remove all triplets from the array. If you want to remove only the first triplet, then do:
a.group_by{|i| i}.values.map{|a| a.length > 2 ? a[0, a.length - 3] : a }.inject([], :+)
Note: This might mess up the order of the array:
[1,2,1,2,3] #=> [1,1,2,2,3]
Let me know if you need to keep the order and, if so, which elements need to be removed if there are more than three, e.g. what should say: [1,1,2,1,1,] - [1,2] or [2,1]?
x.group_by{|i| i }.values.select{|a| a.size >= 3 }.each{|a| c=[3,a.size].min; x.delete_if{|e| a[0]==e && (c-=1)>=0 } }
It will remove the first [3,a.size].min occurrences of a[0] from the input x where a is, for example, [1,1,1,1] for x = [1,2,1,1,1]
I'd do as below :
def del_first_three(a)
num_to_del = a.find { |e| a.count(e) >= 3 }
return a if num_to_del.nil?
3.times do
ind = a.index { |e| e == num_to_del }
a.delete_at(ind)
end
a
end
del_first_three([3,4,5,3,3]) # => [4, 5]
del_first_three([1,5,1,1,1]) # => [5, 1]
del_first_three([1,2,2,4,5]) # => [1, 2, 2, 4, 5]

Calculating Median in Ruby

How do I calculate the median of an array of numbers using Ruby?
I am a beginner and am struggling with handling the cases of the array being of odd and even length.
Here is a solution that works on both even and odd length array and won't alter the array:
def median(array)
return nil if array.empty?
sorted = array.sort
len = sorted.length
(sorted[(len - 1) / 2] + sorted[len / 2]) / 2.0
end
Similar to nbarraille's, but I find it a bit easier to keep track of why this one works:
class Array
def median
sorted = self.sort
half_len = (sorted.length / 2.0).ceil
(sorted[half_len-1] + sorted[-half_len]) / 2.0
end
end
half_len = number of elements up to and including (for array with odd number of items) middle of array.
Even simpler:
class Array
def median
sorted = self.sort
mid = (sorted.length - 1) / 2.0
(sorted[mid.floor] + sorted[mid.ceil]) / 2.0
end
end
If by calculating Median you mean this
Then
a = [12,3,4,5,123,4,5,6,66]
a.sort!
elements = a.count
center = elements/2
elements.even? ? (a[center] + a[center+1])/2 : a[center]
def median(array) #Define your method accepting an array as an argument.
array = array.sort #sort the array from least to greatest
if array.length.odd? #is the length of the array odd?
array[(array.length - 1) / 2] #find value at this index
else array.length.even? #is the length of the array even?
(array[array.length/2] + array[array.length/2 - 1])/2.to_f
#average the values found at these two indexes and convert to float
end
end
More correct solution with handling edge cases:
class Array
def median
sorted = self.sort
size = sorted.size
center = size / 2
if size == 0
nil
elsif size.even?
(sorted[center - 1] + sorted[center]) / 2.0
else
sorted[center]
end
end
end
There is a specs to prove:
describe Array do
describe '#median' do
subject { arr.median }
context 'on empty array' do
let(:arr) { [] }
it { is_expected.to eq nil }
end
context 'on 1-element array' do
let(:arr) { [5] }
it { is_expected.to eq 5 }
end
context 'on 2-elements array' do
let(:arr) { [1, 2] }
it { is_expected.to eq 1.5 }
end
context 'on odd-size array' do
let(:arr) { [100, 5, 2, 12, 1] }
it { is_expected.to eq 5 }
end
context 'on even-size array' do
let(:arr) { [7, 100, 5, 2, 12, 1] }
it { is_expected.to eq 6 }
end
end
end
I like to use Refinements, which is a safe way to Monkey Patch the ruby classes without collateral effects over the system.
The usage become much more cleaner than a new method.
With the Refinements you can monkey patch the Array class, implement the Array#median and this method will only be available inside the scope of the class that is using the refinement! :)
Refinements
module ArrayRefinements
refine Array do
def median
return nil if empty?
sorted = sort
(sorted[(length - 1) / 2] + sorted[length / 2]) / 2.0
end
end
end
class MyClass
using ArrayRefinements
# You can use the Array#median as you wish here
def test(array)
array.median
end
end
MyClass.new.test([1, 2, 2, 2, 3])
=> 2.0
def median(array)
half = array.sort!.length / 2
array.length.odd? ? array[half] : (array[half] + array[half - 1]) / 2
end
*If the length is even, you must add the middle point plus the middle point - 1 to account for the index starting at 0
def median(arr)
sorted = arr.sort
if sorted == []
return nil
end
if sorted.length % 2 != 0
result = sorted.length / 2 # 7/2 = 3.5 (rounded to 3)
return sorted[result] # 6
end
if sorted.length % 2 == 0
result = (sorted.length / 2) - 1
return (sorted[result] + sorted[result+1]) / 2.0 # (4 + 5) / 2
end
end
p median([5, 0, 2, 6, 11, 10, 9])
Here's a solution:
app_arry = [2, 3, 4, 2, 5, 6, 16].sort
# check array isn't empty
if app_arry.empty? || app_arry == ""
puts "Sorry, This will not work."
return nil
end
length = app_arry.length
puts "Array length = #{length}"
puts "Array = #{app_arry}"
if length % 2 == 0
# even number of elements
puts "median is #{(app_arry[length/2].to_f + app_arry[(length-1)/2].to_f)/2}"
else
# odd number of elements
puts "median is #{app_arry[(length-1)/2]}"
end
OUTPUT
Array length = 7
Array = [2, 3, 4, 2, 5, 6, 16]
median is 2
def median(array, already_sorted=false)
return nil if array.empty?
array = array.sort unless already_sorted
m_pos = array.size / 2
return array.size % 2 == 1 ? array[m_pos] : mean(array[m_pos-1..m_pos])
end
There are many ways to do this, but for both performance and reliability, I suggest using the enumerable-statistics library created by Ruby committer mrkn.
https://github.com/mrkn/enumerable-statistics
require 'enumerable/statistics'
ary = [1,2,3,3,4]
ary.mean # => 2.6
ary.median # => 3
I think it's good:
#!/usr/bin/env ruby
#in-the-middle value when odd or
#first of second half when even.
def median(ary)
middle = ary.size/2
sorted = ary.sort_by{ |a| a }
sorted[middle]
end
or
#in-the-middle value when odd or
#average of 2 middle when even.
def median(ary)
middle = ary.size/2
sorted = ary.sort_by{ |a| a }
ary.size.odd? ? sorted[middle] : (sorted[middle]+sorted[middle-1])/2.0
end
I used sort_by rather than sort because it's faster: Sorting an array in descending order in Ruby.

How to create custom iterator for Range

I'd like to create a subclass of Range in order to specify a step size other than 1 so I can do things like:
>> a = RangeWithStepSize.new(-1, 2, 0.5).each {|x| puts(x)}
-1.0
-0.5
0.0
0.5
1.0
1.5
2.0
=> -1..2
My first attempt at an implementation doesn't work:
class RangeWithStepSize < Range
attr_reader :step_size
def initialize(start_v, end_v, step_size, exclusive = false)
super(start_v, end_v, exclusive)
#step_size = step_size
end
def each
self.step(step_size).each
end
end
>> a = RangeWithStepSize.new(-1, 2, 0.5).each {|x| puts(x)}
=> #<Enumerator: [-1.0, -0.5, 0.0, 0.5, 1.0, 1.5, 2.0]:each>
It appears that RangeWithStepSize#each is returning a valid enumerator, but it doesn't enumerate. Any idea why?
<aside>This may be related, but I notice is that Range#step without a block does NOT return an enumerator as specified in the documentation; it returns an array instead:
>> Range.new(-1, 2).step(0.5).class
=> Array
An Array is enumerable, but it is not an Enumerator. Is this a documentation bug?</aside>
clarification
I'd like to make a version of Range that encapsulates a step size, so I can do:
a = RangeWithStepSize(-1, 2, 0.5)
b = RangeWithStepSize(-1, 2, 0.25)
... so enumerating on a produces a step size of 0.5 and b produces 0.25.
You know you can do this, right? Inheritance isn't necessary here.
(-1..2).step(0.5) do |x|
puts x
end
Your code will work with a few small adjustments:
class RangeWithStepSize < Range
attr_reader :step_size
def initialize(start_v, end_v, step_size, exclusive = false)
super(start_v, end_v, exclusive)
#step_size = step_size
end
def each (&block)
self.step(step_size).each(&block)
end
end

can't convert Array into Integer

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.
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 }
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
Have you looked at the output of #chem_species.map { |chem| chem.scan(/[A-Z]/)} (or the second one for that matter)? It's giving you an array of arrays, so if you really wanted to stick with this approach you'd have to do x[0][0].
Instead of mapping, do each
#chem_species.each { |c| c.scan(/[A-Z]/) }
Edit: just realized that that didn't work at all how I had thought it did, my apologies on a silly answer :P
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
(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

Resources