Home > OS >  How to do a cumulative count for array of values in Ruby?
How to do a cumulative count for array of values in Ruby?

Time:04-05

I've got the following data set:

{
  Nov 2020=>1, 
  Dec 2020=>2, 
  Jan 2021=>3, 
  Feb 2021=>4, 
  Mar 2021=>5, 
  Apr 2021=>6
}

Using the following code:

cumulative_count = 0
count_data = {}
    
data_set.each { |k, v| count_data[k] = (cumulative_count  = v) }

I'm producing the following set of data:

{
  Nov 2020=>1,
  Dec 2020=>3,
  Jan 2021=>6,
  Feb 2021=>10,
  Mar 2021=>15,
  Apr 2021=>21
}

Even though I've got the each as a single line, I feel like there's got to be some way to do the entire thing as a one-liner. I've tried using inject with no luck.

CodePudding user response:

input = {
  'Nov 2020' => 1,
  'Dec 2020' => 2,
  'Jan 2021' => 3,
  'Feb 2021' => 4,
  'Mar 2021' => 5,
  'Apr 2021' => 6
}

If it must be on one physical line, and semicolons are allowed:

t = 0; input.each_with_object({}) { |(k, v), a| t  = v; a[k] = t }

If it must be on one physical line, and semicolons are not allowed:

input.each_with_object({ t: 0, data: {}}) { |(k, v), a| (a[:t]  = v) and (a[:data][k] = a[:t]) }[:data]

But in real practice, I think it's easier to read on multiple physical lines :)

t = 0
input.each_with_object({}) { |(k, v), a|
  t  = v
  a[k] = t
}

CodePudding user response:

input = {
  'Nov 2020' => 1,
  'Dec 2020' => 2,
  'Jan 2021' => 3,
  'Feb 2021' => 4,
  'Mar 2021' => 5,
  'Apr 2021' => 6
}

If, as in the example, the values begin at 1 and each after the first is 1 greater the the previous value (recall key/value insertion order is guaranteed in hashes), the value n is to be converted to 1 2 ... n, which, being the sum of an arithmetic series, equals the following.

input.transform_values { |v| (1 v)*v/2 }
  #=> {"Nov 2020"=>1, "Dec 2020"=>3, "Jan 2021"=>6, "Feb 2021"=>10,
  #    "Mar 2021"=>15, "Apr 2021"=>21}

Note that this does not require Hash#transform_values to process key-value pairs in any particular order.


On the other hand, if the values are arbitrary integers, one may write the following, which satisfies your requirement technically, though perhaps not in spirit.

(cumulative = 0) && input.transform_values { |v| cumulative  = v }
  #=> {"Nov 2020"=>1, "Dec 2020"=>3, "Jan 2021"=>6, "Feb 2021"=>10,
  #    "Mar 2021"=>15, "Apr 2021"=>21}
  • Related