40
\$\begingroup\$

In Vernor Vinge's excellent and fascinating book A Deepness in the Sky (which, by the way, I highly recommend1), the Qeng Ho, a culture that spans various star systems, has no notion of "days," "months," "years," etc., and hence has a unique timekeeping system that measures time entirely in seconds. The most commonly used units are the Ksec (kilosecond), Msec (megasecond), and Gsec (gigasecond). Here's a handy chart from my own copy of the book (since I can't find it online):

handy chart

You are currently flying on the Pham Nuwen, and you have just received a message from a strange, unknown planet called "Earth."2 They use different time units than you do, and your computers don't recognize theirs. As the resident Programmer-Archaeologist of the ship, your job is to patch the time-handling code so that it recognizes Earth units of time.

Naturally, since you're only out of coldsleep for another few Ksecs, you want to make your code as short as possible so it can be written quickly. Fortunately, as an interstellar trading culture, the Qeng Ho has access to every programming language invented.

Input

The input will be a single string containing one or more space-separated components. A component is defined as an integer number > 0 and ≤ 255, then a space, and then one of second, minute, hour, day, week, month, year, decade, or century, possibly plural (with an added s, or centuries for the last case).

Here are some valid example inputs:

10 days 12 hours
1 year
184 centuries 1 second
9 weeks 6 days 2 hours 1 minute 20 seconds

You may assume the following about the input:

  • Pluralization of units will always agree with the relevant number.

  • If there are multiple components in the input, they will always be in descending order of length.

Here are what the various input units mean, for the purposes of this challenge:

unit     relative    absolute
---------------------------------------
second   1 second    1 second
minute   60 seconds  60 seconds
hour     60 minutes  3600 seconds
day      24 hours    86400 seconds
week     7 days      604800 seconds
month    30 days     2592000 seconds
year     365 days    31536000 seconds
decade   10 years    315360000 seconds
century  10 decades  3153600000 seconds

Output

Here are the Qeng Ho units which your code has to support:

unit    relative      absolute
----------------------------------------
second  1 second      1 second
Ksec    1000 seconds  1000 seconds
Msec    1000 Ksecs    1000000 seconds
Gsec    1000 Msecs    1000000000 seconds

Use the following algorithm to determine your code's output:

  • First, add up the total amount of time that the input represents.

  • Find the largest Qeng Ho unit that is shorter or the same amount of time as the input—essentially, find the largest unit that there is at least one of.

  • Convert the total amount of time given in the input into this unit, and output the result, rounded to three decimal places.

You may have your choice of which of the following methods to use: rounding up, rounding down, rounding away from zero, or rounding towards ∞ or -∞. If the rounded result ends in 0, you may either remove trailing zeroes or keep as many as you want (or do both, depending on the input).

If the rounded result is exactly 1.000, you must use the singular form (second, Ksec, Msec, Gsec); otherwise, use the plural form (seconds, Ksecs, Msecs, Gsecs).

In certain edge-cases, you might be using the unit of, for example, Ksec, but obtain a rounded result of 1000.000 Ksecs. In this case, you may simply output 1000.000 Ksecs instead of 1 Msec.

You may always assume that the input is in descending order of units (century, decade, year, etc.); furthermore, the component that comes after any given unit will always be shorter (that is, 1 decade 20 years is invalid input).

Test cases

Note: results marked with an asterisk (*) may vary by a negligible amount due to rounding differences.

input                                         output
-------------------------------------------------------------
1 hour                                        3.600 Ksecs
1 day                                         86.400 Ksecs
2 weeks                                       1.210 Msecs
1 year                                        31.536 Msecs
32 years                                      1.009 Gsecs   *
1 second                                      1.000 second
1 century 6 decades                           5.046 Gsecs   *
255 centuries                                 804.168 Gsecs
2 weeks 6 days 1 hour 19 minutes 4 seconds    1.733 Msecs
1 week 3 days 3 hours 7 minutes               875.220 Ksecs
1 week 4 days 13 hours 46 minutes 40 seconds  1.000 Msec
2 months 2 hours                              5.191 Msecs   *
16 minutes 39 seconds                         999.000 seconds

Rules

  • This is , so the shortest code in bytes wins.

1: only if you like hard scifi, of course. In which case I recommend reading A Fire Upon the Deep first, which is (in my opinion) even more fantastic.

2: well, technically "Old Earth" is mentioned several times in A Deepness in the Sky, but...

\$\endgroup\$
15

8 Answers 8

7
\$\begingroup\$

JavaScript (ES6) 255

f=s=>(s=s.replace(/(\d+) (..)/g,(_,n,u)=>t+={se:1,mi:60,ho:3600,da:86400,we:604800,mo:2592e3,ye:31536e3,de:31536e4,ce:31536e5}[u]*n,t=0),[a,b]=t>=1e9?[t/1e9,' Gsec']:t>=1e6?[t/1e6,' Msec']:t>999?[t/1e3,' Ksec']:[t,' second'],a.toFixed(3)+b+(a-1?'s':''))  

// test

console.log=x=>O.innerHTML+=x+'\n'

;[
 ['1 hour','3.600 Ksecs']
,['1 day','86.400 Ksecs']
,['2 weeks','1.210 Msecs']
,['1 year','31.536 Msecs']
,['32 years','1.009 Gsecs'] 
,['1 second','1.000 second']
,['1 century 6 decades','5.046 Gsecs']
,['255 centuries','804.168 Gsecs']
,['2 weeks 6 days 1 hour 19 minutes 4 seconds','1.733 Msecs']
,['1 week 3 days 3 hours 7 minutes','875.220 Ksecs']
,['1 week 4 days 13 hours 46 minutes 40 seconds', '1.000 Msec']
,['2 months 2 hours', '5.191 Msecs']
,['16 minutes 39 seconds', '999 seconds']
].forEach(t=>console.log(t[0]+' '+f(t[0])+' (Check:'+t[1]+')'))
<pre id=O></pre>

\$\endgroup\$
6
\$\begingroup\$

APL (Dyalog APL), 157 156 154 151 154 141 142 bytes

{∊(3⍕N)' '((B/S⊃' KMG')'sec','ond'/⍨~B←T≥1E3),'s'/⍨1≠N←T÷1E3*S←⌊1E3⍟T←+/×/↑⍎¨'\d+ .a?i?'⎕S'&'⊢⍵⊣c←10×d←10×⊃y m w←365 30 7×da←24×h←×⍨mi←60×s←1}

Thanks to ngn for shaving off 13 bytes.

Must have ⎕IO←0, which is default in many APLs.

Try it online!

{} "dfn"; argument is :

s← let s be 1

mi←60× let mi be 60 times that (i.e. 60)

h←×⍨ let h be that squared (lit. that multiplied by itself, i.e. 3600)

da←24× let da be 24 times that (i.e. 86400)

y m w←365 30 7× let y, m, and w be 365, 30, and 7 times that, respectively (i.e. 31536000, 2592000, and 604800)

d←10×⊃ let d be 10 times the first of those (i.e. 315360000)

c←10× let c be 10 times that (i.e. 3153600000)

⍵⊣ discard that in favour of the given argument

'\d+ .a?i?'⎕S'&'⊢Search for, and extract all runs of digits followed by a space, any character, optionally an "a", and optionally an "i"

⍎¨ execute each as APL code (gives pairs of counts and units in seconds)

 mix the pairs into a 2-column matrix.

×/ multiply along the rows, yielding a list of counts of seconds

T←+/ let T be the sum of those (the total count of seconds)

1E3⍟\$\log_{10}\$ of that

N←⌊ let N be the floor of that (i.e. the order of magnitude of T)

1≠ yield 1 if that is different from 1, but 0 otherwise

's'/⍨ use that to replicate the letters of the string "s" (yielding "s" if we need a plural)

(33), prepend the following three things:

  1. 3⍕N the string consisting of N rounded to 3 decimal places

  2. ' ' a space

  3. B←T≥1E3 let B be 1 if T is at least 1000, but 0 otherwise

     ~ logically negate that (i.e. 0 if T is at least 1000, but 1 otherwise)

     'ond'/⍨ use that to replicate the string "ond" (for the full word if there's no prefix)

     '()'sec', prepend the 2-element list consisting of the following and the string "sec":

      S⊃' KMG' use S as an index to select a prefix from the string " KMG"

      B/ replicate the letters of that by B (empty out S if B is 0, but leave as-is if it is 1)

enlist (flatten until a simple string)

\$\endgroup\$
3
  • \$\begingroup\$ If you assign 1E3 to a name (like z), in the first instance you have wasted two chars, in the second instance you have already saved one, and from the third instance onward you are saving two chars. Don't you? \$\endgroup\$
    – lstefano
    Jun 28, 2016 at 11:20
  • \$\begingroup\$ @lstefano No, the first will cost 4: ⌊1E3⍟⌊(z←1E3)⍟ and then save 2 on each of the next 1E3z. \$\endgroup\$
    – Adám
    Jun 28, 2016 at 12:14
  • \$\begingroup\$ Yup, absolutely right. And given that there are only 3 of them, no gain. Sorry for the noise. \$\endgroup\$
    – lstefano
    Jun 28, 2016 at 12:57
2
\$\begingroup\$

Python, 366 363 bytes

d={};l=1;q=str.replace;i=q(raw_input(),"ie","y")
for u,t in zip('second minute hour day week month year decade century'.split(),(1,60,60,24,7,30./7,73./6,10,10)):l=t*l;d[u]=d[u+"s"]=l
while" "in i:
 i=q(q(i," ","*",1)," ","+",1)
q=eval(i,d);f={};l=1
for u in('second','Ksec','Msec','Gsec'):
 l*=1e3
 if q<l:q=q*1e3/l;print"%.3f %s%s"%(q,u,("s","")[q<1.001]);break
\$\endgroup\$
1
  • \$\begingroup\$ You have unnecessary indentation in q=eval(i,d);f={};l=1 line, which breaks code. Besides, you can save 2 bytes by using 10. and 73. instead of 10.0 and 73.0. Also, there's no need for space after print. \$\endgroup\$
    – aland
    Nov 6, 2015 at 12:44
2
\$\begingroup\$

SpecBAS - 476 471 bytes

Because nothing says "cower before our technological superiority" better than line numbers and GOTO statements :-)

1 INPUT e$: DIM t$(SPLIT e$,NOT " "): DIM m=31536e5,31536e4,31536e3,2592e3,604800,86400,3600,60,1
2 LET q=0,n$=" cedeyemowedahomise"
3 FOR i=1 TO ARSIZE t$() STEP 2: LET t=VAL t$(i),u$=t$(i+1)( TO 2),p=POS(u$,n$)/2: INC q,t*m(p): NEXT i
4 IF q>=1e9 THEN LET r=q/1e9,r$=" G": GO TO 8
5 IF q>=1e6 THEN LET r=q/1e6,r$=" M": GO TO 8
6 IF q>999 THEN LET r=q/1e3,r$=" K": GO TO 8
7 IF q<1e3 THEN LET r=q,r$=" "
8 PRINT USING$("&.*0###",r);r$;"sec"+("ond" AND q<1e3)+("s" AND r>1)
\$\endgroup\$
1
\$\begingroup\$

C# (in LinqPad as Function), 460 Bytes

void Main(){var x=Console.ReadLine().Split(' ');long s=0,v,i=0;for(;i<x.Length;){v=long.Parse(x[i++]);var w=x[i++].Substring(0,2);s+=w=="ce"?v*3153600000:w=="de"?v*315360000:w=="ye"?v*31536000:w=="mo"?v*2592000:w=="we"?v*604800:w=="da"?v*86400:w=="ho"?v*3600:w=="mi"?v*60:v;}decimal k=1000,m=k*k,g=m*k,r=0;var o="sec";r=s/g>=1?s/g:s/m>=1?s/m:s/k>=1?s/k:s;o=s/g>=1?"G"+o:s/m>=1?"M"+o:s/k>=1?"K"+o:o+"ond";Console.WriteLine(Math.Round(r,3)+" "+o+(r==1?"":"s"));}

ungolfed:

void Main()
{
    var x=Console.ReadLine().Split(' ');
    long s=0,v,i=0;
    for(;i<x.Length;)
    {
        v=long.Parse(x[i++]);
        var w=x[i++].Substring(0,2);
        s+=w=="ce"?v*3153600000:w=="de"?v*315360000:w=="ye"?v*31536000:w=="mo"?v*2592000:w=="we"?v*604800:w=="da"?v*86400:w=="ho"?v*3600:w=="mi"?v*60:v;
    }
    decimal k=1000,m=k*k,g=m*k,r=0;
    var o="sec";
    r=s/g>=1?s/g:s/m>=1?s/m:s/k>=1?s/k:s;
    o=s/g>=1?"G"+o:s/m>=1?"M"+o:s/k>=1?"K"+o:o+"ond";
    Console.WriteLine(Math.Round(r,3)+" "+o+(r==1?"":"s"));
}
\$\endgroup\$
1
\$\begingroup\$

Mathematica 296 281 bytes

h: After breaking up the input string into a list of quantity magnitudes and units, Capitalize and Pluralize convert the input units into Mathematica Quantity's, from which the total number of seconds is derived.

d converts seconds to the appropriate units. The final s is removed if the time corresponds to 1 unit (of any kind).

With minor adjustments in the code, this approach should work for conversion of natural language input into any measurement system, conventional or not.

h=Tr[UnitConvert[Quantity@@{ToExpression@#,Capitalize@Pluralize@#2},"Seconds"]&@@@Partition[StringSplit@#,2]][[1]]&;
d=ToString[N@#/(c=10^{9,6,3,0})[[p=Position[l=NumberDecompose[#,c],x_/;x>0][[1,1]]]]]<>StringDrop[{" Gsecs"," Msecs"," Ksecs"," seconds"}[[p]],-Boole[Tr[l]==1]]&
z=d@h@#&;

Put into table form:

z1[n_]:={n,z@n}

Grid[z1 /@ {"1 hour", "2 day", "2 weeks", "1 year", "32 years", 
   "1 second", "1 century 6 decades", "255 centuries", 
   "2 weeks 6 days 1 hour 7 minutes", 
   "1 week 3 days 3 hours 46 minutes 40 seconds", 
   "1 week 4 days 13 hours 46 minutes 40 seconds", "2 months 2 hours",
    "16 minutes 39 seconds"}, Alignment -> Right]

pic

\$\endgroup\$
0
\$\begingroup\$

Haskell, 565 555 bytes

import Data.List
import Numeric
import Data.Bool
i=isPrefixOf
s x=showFFloat(Just 3)x""
r=read
f=fromIntegral
b=bool"s"""
c=b.(=="1.000")
h(c:u:l)
 |i"s"u=(r c)+h l
 |i"mi"u=(r c*60)+h l
 |i"h"u=(r c*3600)+h l
 |i"da"u=(r c*86400)+h l
 |i"w"u=(r c*604800)+h l
 |i"mo"u=(r c*2592000)+h l
 |i"y"u=(r c*31536000)+h l
 |i"de"u=(r c*315360000)+h l
 |True=(r c*3153600000)+h l
h _=0
q i
 |v<-s((f i)/10^9),i>=10^9=v++" Gsec"++c v
 |v<-s((f i)/10^6),i>=10^6=v++" Msec"++c v
 |v<-s((f i)/1000),i>=1000=v++" ksec"++c v
 |True=show i++" second"++b(i==1)
t=q.h.words

I'm reasonably sure I'm missing so many golfing opportunities here…The price of being a golfing beginner I guess.

My answer is a function taking a string containing the Earth time as an input parameter and returning the Qeng Ho time.

P.S. : I stupidly forgot about the 3 digits precision…which does drive the byte count up.

P.P.S. : Better chosen top-level expressions shaved off 10 bytes…and it should now be accurate to boot.

\$\endgroup\$
0
\$\begingroup\$

Matlab 315 bytes

K='cedeyemowedahomiseconds';Q=' KMGT';for j=1:9;y(j)=double(~isempty(strfind(S,K(2*j-1:2*j))));end
y(y==1)=sscanf(S,repmat('%d %*s ',1,9));y=86400*sum(datenum([sum(y(1:3)*10.^[2;1;0]),y(4),y(5:6)*[7;1],y(7:9)]));z=floor(log10(y)/3);y=num2str(y/10^(3*z)+1e-4);[y(1:4),' ',Q(z+1),K(17:23-(y(1:4)=='1.00'))]

Test:

S = '2 centuries 1 decade 2 years 3 months 3 weeks 4 days 1 hour 44 minutes 58 seconds';

Output:

ans =
6.69 Gseconds
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.