Bruno Fernandes
Bruno Fernandes

Reputation: 145

How to divide two Array of Arrays and prevent zero division

I have the following sample data.

Basically I need to divide each data in the variable distribuitions by the data field in the variable entrance.

distribuitions = [{:name=>"cardio", :data=>[["06", 0], ["07", 0], ["08", 0], ["09", 0], ["10", 0], ["11", 0.39e2], ["12", 0], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]},
 {:name=>"shape", :data=>[["06", 0], ["07", 0], ["08", 0], ["09", 0], ["10", 0], ["11", 0.394e2], ["12", 0], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]}]

entrance = {:name=>"gym_entrance", :data=>[["06", 0], ["07", 0], ["08", 0], ["09", 0], ["10", 0], ["11", 0.176e3], ["12", 0], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]}

I have written this function but I can't prevent 0 division.

def divide_distribuition_set_by(distribuitions, d)
  distribuitions.each do |distribuition|
    collection = [distribuition[:data], d]
    hours, attendance = collection.map { |x| x.transpose }.transpose
    hours.first.zip(attendance.transpose.map { |col| col.reduce(:/) })
  end
end

Expected result ( all values of data are divided by the field data in the entrance variable

[{:name=>"cardio", :data=>[["06", 0], ["07", 0], ["08", 0], ["09", 0], ["10", 0], ["11", 0.2215909090909091,], ["12", 0], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]},
     {:name=>"shape", :data=>[["06", 0], ["07", 0], ["08", 0], ["09", 0], ["10", 0], ["11", 0.22386363636363635,], ["12", 0], ["13", 0], ["14", 0], ["15", 0], ["16", 0], ["17", 0], ["18", 0], ["19", 0], ["20", 0], ["21", 0], ["22", 0], ["23", 0]]}]

Upvotes: 0

Views: 517

Answers (3)

Cary Swoveland
Cary Swoveland

Reputation: 110685

I assume that 0/0 is to be regarded as zero. For n/0 I will return :zd when n is non-zero.

distros  = [{:name=>"cardio", :data=>[["06", 0], ["07", 3], ["08", 2]]},
            {:name=>"shape",  :data=>[["06", 0], ["07", 2], ["08", 1]]}]
entrance =  {:name=>"gym",    :data=>[["08", 2], ["07", 0], ["06", 0]]}

First create a hash g from entrance[:data].

g = entrance[:data].to_h
  #=> {"06"=>0, "07"=>0, "08"=>2}

Next, prepare a helper method.

def divide_em(num, den)
  if den.zero?
    num.zero? ? 0.0 : :zd
  else
    num.to_f/den
  end
end

divide_em(1, 2) #=> 0.5
divide_em(0, 0) #=> 0.0
divide_em(3, 0) #=> :zd

Lastly, perform the divisions.

distros.map { |h| h.merge(h) { |k,d,_| k==:data ?
  d.map { |id, num| divide_em(num, g[id]) } : d } }
  #=> [{:name=>"cardio", :data=>[0, :zd, 1.0]}, 
  #    {:name=>"shape",  :data=>[0, :zd, 0.5]}]         

Note: 1) the order of the arrays in entrance[:data] need not correspond to the order of the arrays in each h[:data] where h is an element of distros; and 2) distros remains unchanged.

This employs the version of Hash#merge that uses a block to determine the values of keys that are present in both hashes being merged, which here is of course all keys. See the doc for details, particularly the definitions of the resolution block's three variables (here k, d and _, the underscore indicating that the third block variable is not used).

Upvotes: 0

steenslag
steenslag

Reputation: 80065

Quick and dirty: keep everything as it is, only replace this line

hours.first.zip(attendance.transpose.map { |col| col.reduce(:/) })

by this

hours.first.zip(attendance.transpose.map { |col| col.reduce(:/) rescue 0 })

Upvotes: 1

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

edata = entrance[:data].to_h
distribuitions.map do |hash|
  hash.map do |k, v|
    [k, if k == :data
          v.to_h.merge(edata) { |_, v1, v2| v2.zero? ? 0 : v1.to_f/v2.to_f }
        else
          v
        end]
  end.to_h
end

Upvotes: 0

Related Questions