Sum the time durations

18

Challenge

Write the shortest code that can sum all the time durations that appear in the stdin. The program must only consider the strings that match with one of the following patterns and ignore the rest.

    HH:MM:SS     (it will be interpreted as HH hours, MM minutes and SS seconds)        
    H:MM:SS      (it will be interpreted as H hours, MM minutes and SS seconds)
    MM:SS        (it will be interpreted as MM minutes, SS seconds)
    M:SS         (it will be interpreted as M minutes, SS seconds)

examples of strings that match with the enumerated patterns:

    12:00:01  
    2:03:22  
    00:53  
    9:13

The output should be of the form

    HHh MMm SSs      (that means HH hours, MM minutes and SS seconds with non-zero-padding)

Example

STDIN

View the Welcome video.
Video: 10:37 min.
View the video introduction to the course.
Video: 3:30 min. View the video of how to use the Lesson Overview.
Video: 9:13 min.
View the video overview of how to use the Epsilen system to share your work.
Video: 03:15 min.
View the video to learn about the State of Texas Assessment of Academic Readiness (STAAR).
Video: 1:05:26 min.

STDOUT

1h 32m 1s

Alfredo Diaz

Posted 2014-12-11T09:59:53.817

Reputation: 303

time is a special topic to me... I love this question! – ojblass – 2014-12-15T19:40:30.873

What about strings like 10:4:56? According current specification they have to be treated as 4m 56s, part 10 will be ignored. Same question about 10:12:7 does it mean 10m 12s with ignoring of 7? Or handling of such strings can be implementation defined? – Qwertiy – 2014-12-11T10:19:36.107

The program should only consider time durations with zero-padding in the minute and second fields. In your example the string "10:4:56" will be treated as 4m 56s. Also the string "10:12:7" will be interpreted as 10m 12s. – Alfredo Diaz – 2014-12-11T10:27:55.273

Strange, but ok :) – Qwertiy – 2014-12-11T10:31:05.750

How did you get 1h 19m 18s in the output? 37+30+13+15+26==121, 10+3+9+3+5==30, 1==1, so I expect 1h 32m 01s. What's wrong in this logic? Also, such output format is that one which is expected, isn't it? – Qwertiy – 2014-12-11T10:58:09.143

You are right. Sorry :S – Alfredo Diaz – 2014-12-11T11:05:44.963

Answers

3

Pyth 105

K"smh"J"\D\D+|\d+:(?=\d:)|:\d\D"W:QJ1=Q:QJd;FN_msdCfn2lTm+*]0</k\:2msbck\:cQ)~k+d+hK_`%+NZ60=Z/N60=KtK;_k

Try it online.

This requires input from STDIN in the same way that the Javascript answer does, as quoted text with newlines as \ns.

Sample:

"View the Welcome video.\nVideo: 10:37 min.\nView the video introduction to the course.\nVideo: 3:30 min. View the video of how to use the Lesson Overview.\nVideo: 9:13 min.\nView the video overview of how to use the Epsilen system to share your work.\nVideo: 03:15 min.\nView the video to learn about the State of Texas Assessment of Academic Readiness (STAAR).\nVideo: 1:05:26 min."

Output

1h 32m 1s

Example working with weirder dates:

"10:10:5 and 5:1:10 and 27 or 16: or 1:1:1 or 11:1\n"

Output

0h 11m 20s

(Only the 10:10 and the 1:10 are legitimate times)

The main reason that this is so long is that Pyth won't let you extract positive matches. This instead matches everything that isn't a valid time, and replaces it with a space character. Then, splitting on whitespace leaves only times and some wayward numbers. The excess numbers are removed by checking for : characters, which will have been removed from non-valid times. This could almost certainly be golfed further ;)

FryAmTheEggman

Posted 2014-12-11T09:59:53.817

Reputation: 16 206

Lucky bastard that Pyth has regex too! – Optimizer – 2014-12-12T14:31:08.600

@Optimizer :D It was a real pain though. I'm thinking of suggesting changing the "is match" behaviour to change based on the arg you give it (currently it only checks that it is a non-string) – FryAmTheEggman – 2014-12-12T14:36:27.237

6

Javascript ES6, 138 chars

Function, 139

Takes string as an argument and writes output to console:

f=s=>(r=0,s.replace(/(\d\d?):(\d\d)(:(\d\d))?/g,(m,a,b,x,c)=>r+=x?+c+b*60+a*3600:+b+a*60),console.log("%dh %dm %ds",r/3600,r%3600/60,r%60))

Program, 138

prompt(r=0).replace(/(\d\d?):(\d\d)(:(\d\d))?/g,(m,a,b,x,c)=>r+=x?+c+b*60+a*3600:+b+a*60),console.log("%dh %dm %ds",r/3600,r%3600/60,r%60)

Test for function

f("View the Welcome video.\n\
Video: 10:37 min.\n\
View the video introduction to the course.\n\
Video: 3:30 min. View the video of how to use the Lesson Overview.\n\
Video: 9:13 min.\n\
View the video overview of how to use the Epsilen system to share your work.\n\
Video: 03:15 min.\n\
View the video to learn about the State of Texas Assessment of Academic Readiness (STAAR).\n\
Video: 1:05:26 min.")

Output

"1h 32m 1s"

Qwertiy

Posted 2014-12-11T09:59:53.817

Reputation: 2 697

Ok. Works fine in Firefox Developer Edition 36.0a2, formatting fails only in Firefox 34.0. – manatwork – 2014-12-11T11:40:07.977

Promt doesn't allow multiline strings. But I can add version with prompt() call in same number of chars :) I even shorten it 1 symbol))) – Qwertiy – 2014-12-11T11:43:45.053

@Optimizer How to enter them? – Qwertiy – 2014-12-11T11:49:43.850

@Optimizer Inserting a new line doesn't work in my FF 35.0. – Qwertiy – 2014-12-11T11:51:48.753

I cannot get it to work. I tried it on ideone.com http://ideone.com/56EHgV

– Alfredo Diaz – 2014-12-11T12:39:07.233

@AlfredoDiaz Just run it in Firefox 35+ console. – Qwertiy – 2014-12-11T12:41:16.650

@Qwertiy Yes. It works even in my Firefox 33 console :) – Alfredo Diaz – 2014-12-11T12:48:39.390

4

JavaScript, ES6, 208 200 197 bytes

I know this is super long, but I wanted to explore the latest features of ES6, reverse, map-reduce, arrow functions and array comprehension (spread operator).

alert(prompt().match(/\d\d?:\d\d(:\d\d)?/g).map(x=>[...x.split(":").reverse(),z=0].slice(0,3)).reduce((a,b)=>b.map((y,i)=>+y+ +a[i])).map((x,i)=>(z=(t=x+z|0)/60,t%60+"smh"[i])).reverse().join(" "))

Just run the snippet in a latest Firefox.

How it works (ungolfed a bit)

alert(                              // Alert the final result
  prompt()                          // Take the input via prompt
  .match(/\d\d?:\d\d(:\d\d)?/g)     // Match only correct time formats
  .map(                             // Map all matches using this method
    x=>[                            // Take each element as argument x
      ...x.split(":").reverse(),    // split x on ":" and reverse the array, then spread it
      z=0                           // put 0 as last element of return array
    ].slice(0,3)                    // Take only first 3 elements of the array
  ).reduce(                         // Reduce the result using this method
    (a,b)=>                         // Pairwise elements of the array
    b.map(                          // Map array b
      (y,i)=>~~y+~~a[i]             // Convert b[i] to b[i]+a[i]
    )                               // Now we have array like [SS, MM, HH]
  ).map(                            // Map these three values for carry over calculation
    (x,i)=>(
      t=x+z,                        // z contains carryover amount, add it to this value
      z=(t/60)|0,                   // Carryover is now floor(t/60)
      t%60+"smh"[i]                 // Remove overflow from t and add "s", "m" or "h"
    )                               // Now we have array like ["SSs", "MMm", "HHh"]
  ).reverse().join(" ")             // Reverse it and join by space
)

Optimizer

Posted 2014-12-11T09:59:53.817

Reputation: 25 836

4

Bash (with grep, sed, awk and date): 124 bytes, 120 bytes

Just pipe the text into this:

grep -o '[:0-9]*'|sed 's/^[^:]*:[^:]*$/:\0/'|awk -F: '{T+=3600*$1+60*$2+$3}END{print"@"T}'|xargs date +"%Hh %Mm %Ss" -ud

How it works

  • grep: outputs strings from the input containing only 0123456789:
  • sed: turns MM:SS and M:SS into :M:SS
  • awk: calculates the seconds, empty string is 0
  • xargs: passes input as argument to date
  • date: converts seconds since epoch (prefixed with @) to the required format

pgy

Posted 2014-12-11T09:59:53.817

Reputation: 830

You are right, nice catch :) Added -u flag. – pgy – 2014-12-12T00:56:03.557

Isn't this hour related to your timezone? – Qwertiy – 2014-12-11T20:52:19.353

3

Perl - 228 201

use integer;$h=0,$m=0,$s=0;while(<>){if(/(\d+:){1,2}\d+/){@a=reverse(split(/:/,$&));push @a,(0)x(3-@a);$s+=@a[0];$m+=@a[1];$h+=@a[2];}}$m+=$s/60;$s=$s%60;$h+=$m/60;$m=$m%60;print $h."h ".$m."m ".$s."s"

It happens to be the same algorithm as Optimizer's (grep,split,reverse,add).

I am no Perl expert, so maybe the byte count can be reduced.

Ungolfed

use integer;                              # will do integer division
$h=0,$m=0,$s=0;
while(<>){
    if(/(\d+:){1,2}\d+/) {                # extract date formats
        @a = reverse(split(/:/,$&));      # split by ":" and reverse
        push @a,(0)x(3-@a);               # pad with zeros (minutes and hours)
        $s+=@a[0];                        # sum seconds
        $m+=@a[1];                        # sum minutes
        $h+=@a[2];                        # sum hours
    }
}

# convert seconds as minutes    
$m += $s / 60;
$s = $s % 60;

# convert minutes as hours
$h += $m / 60;
$m = $m % 60;

print $h."h ".$m."m ".$s."s";

coredump

Posted 2014-12-11T09:59:53.817

Reputation: 6 292

As for me, it's strange to see perl solution longer than javascript one :) – Qwertiy – 2014-12-11T13:47:26.723

Well, if even the shebang is counted, is normal to be longer. – manatwork – 2014-12-11T13:48:53.503

@Qwertiy I agree. My hope is that some Perl guru will help me fix that. – coredump – 2014-12-11T13:49:23.700

@manatwork Why does it count? – Qwertiy – 2014-12-11T14:03:37.020

@Qwertiy, because coredump forgotten to exclude it from the count. :S Could be just removed (together with all those my keywords). – manatwork – 2014-12-11T14:32:39.160

@manatwork Removed, thanks. – coredump – 2014-12-11T14:37:51.377

3

Rebol - 174

n: charset"1234567890"a:[1 2 n]b:[":"2 n]c: 0 parse input[any[copy x[a b b](c: c + do x)| copy x[a b](c: c + do join"0:"x)| skip]]print reword"$1h $2m $3s"[1 c/1 2 c/2 3 c/3]

Ungolfed + annotated:

n: charset "1234567890"                      ; setup \d regex equiv
a: [1 2 n]                                   ; parse rule for \d{1,2} 
b: [":" 2 n]                                 ; parse rule for :\d\d
c: 0                                         ; time counter

parse input [                                ; parse the input (STDIN)
                                             ; (no regex in Rebol)

  any [                                      ; match zero or more... 
                                             ;
      copy x [a b b] (c: c + do x)           ;  HH:MM:SS or H:MM:SS
                                             ;    - copy match to x
                                             ;    - increment time (c) by x
                                             ; OR
    | copy x [a b] (c: c + do join "0:" x)   ;  MM:SS or M:SS
                                             ;    - copy match to x
                                             ;    - "MM:SS" into "0:MM:SS" (join)
                                             ;    - then increment time (c)
                                             ; OR
    | skip                                   ;   no match so move through input
  ]
]

print reword "$1h $2m $3s" [1 c/1 2 c/2 3 c/3]

Rebol comes with its own time! datatype. You can see how the above code makes use of this from example below (from within the Rebol console):

>> 0:10:37 + 0:3:30 + 0:9:13 + 0:3:15 + 1:05:26
== 1:32:01

;; Rebol would treat 10:37 as 10 hours & 37 minutes (and not MM:SS)
;; So we have to prefix the "0:"

>> join "0:" 10:37
== "0:10:37"

;; This is a string so we use Rebol DO evaluator to convert to time!

>> do join "0:" 10:37 
== 0:10:37

>> type? do join "0:" 10:37
== time!

>> hms: do join "0:" 10:37
== 0:10:37

>> hms/hour
== 0

>> hms/second
== 37

>> hms/minute
== 10

draegtun

Posted 2014-12-11T09:59:53.817

Reputation: 1 592

2

Groovy - 195

M=60
r=(System.in.text=~/((\d?\d):)?(\d\d):(\d\d)/).collect{it[2..4]*.toInteger().inject{s,i->(s?:0)*M+i}}.inject{s,i->s+=i}
f=[];(2..0).each{j=M**it;s=r%j;f<<(r-s)/j;r=s}
printf("%sh %sm %ss",f)

I can't figure out how to compress it more.

Ungolfed

M=60
r=(System.in.text=~/((\d?\d):)?(\d\d):(\d\d)/).collect{  // extract dates
    it[2..4]*.toInteger().inject{ s,i ->                 // convert to seconds
        (s?:0)*M+i
    }
}.inject{s,i ->
    s+=i                                                 // sum seconds
}

f=[];
(2..0).each{                                             // convert to h,m,s
    j=M**it;
    s=r%j;
    f<<(r-s)/j;
    r=s
}

printf("%sh %sm %ss",f)

Alfredo Diaz

Posted 2014-12-11T09:59:53.817

Reputation: 303

1

Mathematica 300 chars

This little exercise took up a lot of code, even for Mathematica. Surely there are more efficient ways to do this.

Golfed

Assuming that the input is stored in txt,

n=NumberString;
t=ToExpression;
o=TimeObject;

QuotientRemainder[QuantityMagnitude[Plus@@((o[#]-o[{0,0,0}])&/@
(StringSplit[StringCases[w,{(n~~":"~~n~~":"~~n),(n~~":"~~n)}],":"]
/.{{a_,b_}:> {0,t@a,t@b},{a_,b_,c_}:> {t@a,t@b,t@c}}))],60]/.{h_,m_}:> 
Row[{h,"h ",IntegerPart@m,"m ",Round[60 FractionalPart[m]],"s "}]

How it works (using unGolfed code):

1-Find the times.

StringCases[txt,{(NumberString~~":"~~NumberString~~":"~~NumberString),
(NumberString~~":"~~NumberString)}];

{"10:37", "3:30", "9:13", "03:15", "1:05:26"}


2-Break into hours, minutes, seconds

StringSplit[%,":"]/.{{a_,b_}:> {0,ToExpression@a,ToExpression@b},{a_,b_,c_}:> 
{ToExpression@a,ToExpression@b,ToExpression@c}}

{{0, 10, 37}, {0, 3, 30}, {0, 9, 13}, {0, 3, 15}, {1, 5, 26}}


3-Sum the times. Time objects are clock times. Subtracting one time object from another returns a duration, in this case 92.0167 minutes. QuantityMagnitude drops the unit of measure.

q=QuantityMagnitude[Plus@@((TimeObject[#]-TimeObject[{0,0,0}])&/@%)]

92.0167


4-Convert 92.0167 minutes into hours, minutes, seconds.

QuotientRemainder[q,60]/.{h_,m_}:> Row[{h,"h ",IntegerPart@m,"m ",
Round[60 FractionalPart[m]],"s "}]

1h 32m 1s

DavidC

Posted 2014-12-11T09:59:53.817

Reputation: 24 524

1

Perl, 146

My entry prints the output with a trailing space - I hope that's ok

while(<>){for(/(\d?\d(?::\d\d){1,2})/g){$m=1;for(reverse split/:/,$_){$t+=$m*$_;$m*=60}}}for('s','m'){$o=($t%60)."$_ $o";$t/=60}print int$t,"h $o"

If we can assume there will be only one time per line of input, we can chop 4 characters:

while(<>){if(/(\d?\d(:\d\d){1,2})/){$m=1;for(reverse split/:/,$&){$t+=$m*$_;$m*=60}}}for('s','m'){$o=($t%60)."$_ $o";$t/=60}print int$t,"h $o"

These work by accumulating the total seconds elapsed and formatting that value afterwards.

KJP

Posted 2014-12-11T09:59:53.817

Reputation: 131