John John - 1 month ago 8
Ruby Question

how to merge the hours minutes and seconds from a multiline string in ruby?

i am getting a

multiline
string in
ruby
. lets say

s = "00:01:07,11-234-090
00:05:01,22-080-080
00:05:00,11-234-090"


this string is separated by new line characters (\n). i want a method which sums up the duration (left side part of comma in every new line ex (00:01:07) based on the string(right side part after the comma in every line) means the first line and last line contains the same string after the comma (11-234-090). and in the second line the string after the comma is (22-080-080) as there is no same string in the entire string i want to keep this line as same.

input = "00:01:07,11-234-090
00:05:01,22-080-080
00:05:00,11-234-090"

output : "00:06:07,11-234-090
00:05:01,22-080-080"


here the
00
represent hour ,
06
represents minutes and
07
represents seconds.

i want a method which returns the desired string by taking the input string

def returs_sumation_string(input_string)
return desired_string
end


i tried this:

s = "00:01:07,400-234-090
00:05:01,701-080-080
00:05:00,400-234-090"

splited = s.split("\n")
splited.map!{|s| s.strip}


this much i know. do not know how to proceed further.

Answer

Code

def aggregate(str)
  str.strip.split(/\n\s*/).group_by { |s| s[-10..-1] }.map do |k,a|
    secs = a.reduce(0) do |t,s|
      h,m,s = s.split(":").map(&:to_i)
      t + 3600*h + 60*m + s
    end
    h,secs = secs.divmod(3600)
    m,secs = secs.divmod(60) 
    "%0#{h>99 ? 3:2 }d:%02d:%02d,%s" % [h,m,secs,k]
  end
end

Example

str = "00:01:07,11-234-090
       00:05:01,22-080-080
       00:05:00,11-234-090"

aggregate(str)
  #=> ["00:06:07,11-234-090", "00:05:01,22-080-080"]

Explanation

See the docs for methods Enumerable#group_by, Enumerable#reduce (aka inject) and Fixnum#divmod.

For str given in the example, the main steps are as follows.

b = str.strip
  #=> "00:01:07,11-234-090\n         00:05:01,22-080-080\n         00:05:00,11-234-090" 
c = b.split(/\n\s*/)
  #=> ["00:01:07,11-234-090", "00:05:01,22-080-080", "00:05:00,11-234-090"]
d = c.group_by { |s| s[-10..-1] }
  #=> {"11-234-090"=>["00:01:07,11-234-090", "00:05:00,11-234-090"],
  #    "22-080-080"=>["00:05:01,22-080-080"]}
d.map do |k,a|
  secs = a.reduce(0) do |t,s|
    h,m,s = s.split(":").map(&:to_i)
    t + 3600*h + 60*m + s
  end
  h,secs = secs.divmod(3600)
  m,secs = secs.divmod(60) 
  "%0#{h>99 ? 3:2 }d:%02d:%02d,%s" % [h,m,secs,k]
end
  #=> ["00:06:07,11-234-090", "00:05:01,22-080-080"]

Now let's break down the last step. map passes the first element of c (an array containing a key-value pair) to the block, to which the block variables are assigned, using parallel assignment (sometimes called multiple assignment):

k, a = d.first
  #=> ["11-234-090", ["00:01:07,11-234-090", "00:05:00,11-234-090"]] 
k #=> "11-234-090" 
a #=> ["00:01:07,11-234-090", "00:05:00,11-234-090"] 

and the block calculation is performed. First we calculate the total number of seconds for the two elements of a. I've added some puts statements to show the calculations.

secs = a.reduce(0) do |t,s|
  puts "  t=#{t}, s=#{s}"
  puts "    h,m,s = #{s.split(":")}"
  h,m,s = s.split(":").map(&:to_i)
  puts "    h=#{h}, m=#{m}, s=#{s}"
  puts "    t + 3600*h + 60*m + s=#{t + 3600*h + 60*m + s}"
  t + 3600*h + 60*m + s
end
# t=0, s=00:01:07,11-234-090
#   h,m,s = ["00", "01", "07,11-234-090"]
#   h=0, m=1, s=7
#   t + 3600*h + 60*m + s=67
# t=67, s=00:05:00,11-234-090
#   h,m,s = ["00", "05", "00,11-234-090"]
#   h=0, m=5, s=0
#   t + 3600*h + 60*m + s=367
#=> 367 

Continuing,

  h,secs = secs.divmod(3600)
    #=> [0, 367] 
  h
    #=> 0 
  secs
    #=> 367 
  m,secs = secs.divmod(60) 
    #=> [6, 7] 
  m
    #=> 6 
  secs
    #=> 7 
  "%0#{h>99 ? 3:2 }d:%02d:%02d,%s" % [h,m,secs,k]
    #=> "00:06:07,11-234-090" 

The final statement, the formatting of the string that is returned, uses format codes listed in the doc for Kernel::format. "%02d" means to format an integer 2 characters wide with zeroes used for any left padding required (e.g., "%02d" % [9] #=> "09"). "%0#{h>99 ? 3:2 }" means that the field width for hours should be 3 if there are more than 99 hours, else the width is 2.

Calculations for the second element of c are similar.

The group_by expression, group_by { |s| s[-10..-1] } means group the strings (produced by split) by the last 10 characters of the string. Alternatively, one could replace the first line with the following.

str.strip.split(/\n\s*/).group_by { |s| s.split(",").last }