12
\$\begingroup\$

Given a 24 hour time, e.g. 20:48, output the nearest time where each adjacent pair of digits has an absolute difference of 1, e.g. 21:01.

That is, each number in the output must differ from the numbers next to it by 1, and the output must be the closest time, forwards or backwards, for which this is true. Time can be considered to wrap around. For example, 23:55 and 00:05 are only 10 minutes apart.

Input

  • Input can be in any reasonable form which represents all four digits of a 24 hour time. E.x. a string, "20:48" (colon optional), a list of integers, [2, 0, 4, 8], or a single integer 2048.
  • Leading zeros are optional for input. Use whatever is most convenient.
  • You can assume the input will always represent a valid 24 hour time.

Output

  • You must output four integers which represent a valid 24 hour time. The individual integers can be represented in whatever form is most convenient. E.x. [2, 1, 0, 1] or "2101".
  • Leading zeros are not optional for output. E.x. you can't output 1:23 instead of 01:23.
  • You may optionally output a separator between the hours and minutes. This can be a colon, period, space, newline, or some other junk that saves you a byte. Whatever separator you choose, it must be consistent from one output to another.
  • If there are two equally close times to the input which satisfy the requirements, you may output either.
  • Note there are only 16 possible outputs: 01:01, 01:21, 01:23, 10:10, 10:12, 12:10, 12:12, 12:32, 12:34, 21:01, 21:21, 21:23, 23:21, 23:23, 23:43, and 23:45.

Test cases

  • 00:10 -> 23:45
  • 01:23 -> 01:23
  • 05:46 -> 01:23
  • 05:47 -> 10:10
  • 12:00 -> 12:10
  • 16:47 -> 12:34
  • 16:48 -> 21:01
  • 20:48 -> 21:01
  • 21:22 -> 21:23 or 21:21

Python 3 reference implementation.

Score

This is code golf, so the shortest program in bytes wins. The standard loopholes are forbidden.

\$\endgroup\$
6
  • \$\begingroup\$ The term "physically" may be a bit misleading ... \$\endgroup\$ May 21, 2020 at 23:39
  • \$\begingroup\$ @JonathanFrech I agree, but I'm not sure alternative terms are clearer. For example, "spatially" still seems strange. Do you have a term in mind? \$\endgroup\$
    – Vaelus
    May 21, 2020 at 23:45
  • 3
    \$\begingroup\$ Maybe you could say "where each adjacent pair of digits has an absolute difference of 1" \$\endgroup\$ May 21, 2020 at 23:47
  • \$\begingroup\$ So, there are only 16 different possible outputs? Or are 0 and 9 considered adjacent? \$\endgroup\$
    – Abigail
    May 22, 2020 at 8:08
  • 1
    \$\begingroup\$ I suggest adding 05:46 -> 01:23, 05:47 -> 10:10, 16:47 -> 12:34, and 16:48 -> 21:01 to the tests since these are the midpoints of the widest intervals. \$\endgroup\$ May 22, 2020 at 18:52

10 Answers 10

10
\$\begingroup\$

JavaScript (ES7),  136  132 bytes

Takes a single integer as input and returns a string.

s=>(V=g=(d,t=s,k=0)=>k>V?R:[...S=([1e3]+t).slice(-4)].some(p=c=>(p-(p=c))**2-1)?g(d,t=(t-~d)%2400,k+=t%100<60):(V=k,R=S))()&&g(2398)

Try it online!

How?

The only valid hour prefixes are 01, 10, 12, 21 and 23. It means that any invalid minute count starting with a digit greater than 5 will be rejected by the test on the absolute difference between two consecutive digits.

So there's no need to explicitly handle the wrapping of minutes modulo 60. Instead, we can just make sure that the distance k is not incremented on invalid minutes:

k += t % 100 < 60

JavaScript (Node.js),  148  146 bytes

Takes a single integer as input and returns a string.

This version picks the answer from a hardcoded table.

t=>0x60553BCD48A3CE4F56D818FDC473FC726398E907D3A9n.toString(6).substr([22,85,9,423,462,x=98,x,9,9,413,461,9,x,x,9,9].findIndex(d=>(t-=d+2)<0)*4,4)

Try it online!

How?

Once converted to base-6, the integer 0x60553BCD48A3CE4F56D818FDC473FC726398E907D3A9n is turned into the following string, which holds all possible valid answers:

"23450101012101231010101212101212123212342101212121232321232323432345"

The correct answer is selected according to the interval in which the input time falls:

 answer | input interval
--------+----------------
 "2345" | 00:00 - 00:23
 "0101" | 00:24 - 01:10
 "0121" | 01:11 - 01:21
 "0123" | 01:22 - 05:46
 "1010" | 05:47 - 10:10
 "1012" | 10:11 - 11:10
 "1210" | 11:11 - 12:10
 "1212" | 12:11 - 12:21
 "1232" | 12:22 - 12:32
 "1234" | 12:33 - 16:47
 "2101" | 16:48 - 21:10
 "2121" | 21:11 - 21:21
 "2123" | 21:22 - 22:21
 "2321" | 22:22 - 23:21
 "2323" | 23:22 - 23:32
 "2343" | 23:33 - 23:43
 "2345" | 23:44 - 23:59

The lengths of the intervals in minutes (minus 2) are stored in an array.

\$\endgroup\$
5
\$\begingroup\$

Python 2, 136 138 133 126 125 122 151 124 108 106 103 101 bytes

f=lambda h,m,i=0:(h,m)*all(j%11%9==1for j in(h,m/10-h%10,m))or f((h+(m+i)/60)%24,(m+i)%60,~i+2*(i<1))

Try it online!

Input is hour,minute parameters result is (hour,minute) tuple

Using recursion

i moves through [0,-1,+2,-3,+4,...] the sum of the initial terms therefore moves through [0,-1,1,-2,2,...]

(h,m) are offset by i minutes moving alternately through nearest above and below time not yet checked.

Check for result by considering h, m and h%10*10+m/10, which is low hour digit and top minute digit.

To check two digit number n in form d (d+1) or (d+1) d, observe n%11 is either 1 or 10 in exactly these cases, and checking for 1 or 10 using %9==1. i.e. n%11%9==1

As this two digit test reduces modulo 11, h%10*10+m/10 is equivalently m/10-h%10.

Previous approach:

def f(h,m,i=0):t=m+i;s='%02d'*2%((h+t/60)%24,t%60);return s*all(a+b in'4321012345'for a,b in zip(s,s[1:]))or f(h,m,-i+(i<1))

Try it online!

Input in integer output is 4 character string.

Formats number as string, taking pairs checks against string table, otherwise recurse with alternating offsets (i=0,1,-1,2,-2,...)

Originally:

Offsets n by [0,1,-1,2,-2,...], formats as four digit string, then taking pairs of characters checks they differ by one by looking up as substring in compressed string table.

+2 bytes Fixed by adding '43' to table.

-5 bytes Removed unnecessary +2400, which was to avoid mod of negative number -- Python's % works properly in that case.

-7 bytes string table improved, and by using / instead of // due to Surculose Sputum.

-1 byte reduce range of search.

-3 bytes Surculose Sputum: remove unecessary [] in all() and compress expression i%2*2-1 to i%-2|1.

+29 bytes code was broken, added correction term for wrapping minutes (n%100+i)/60*40

-27 bytes Surculose Sputum: rewritten using recursion

-16 bytes reworked using new approach

-2 bytes remove introduced variable which was false saving.

-3 bytes simplification for middle digits test.

-2 bytes Surculose Sputum: using lambda instead of def

\$\endgroup\$
12
  • \$\begingroup\$ @math junkie thanks \$\endgroup\$
    – David
    May 22, 2020 at 17:14
  • \$\begingroup\$ That's much nicer. (I was just removing the +2400, as you commented.) \$\endgroup\$
    – David
    May 22, 2020 at 17:54
  • \$\begingroup\$ 123 bytes by removing the redundant [] inside all(), and replacing i%2*2-1 with i%-2|1. Nice approach btw, I really like the compressed table idea. \$\endgroup\$ May 22, 2020 at 18:01
  • \$\begingroup\$ The longest gap is actually 873, between 05:59 and 01:23. So you should use at least range(874). \$\endgroup\$ May 22, 2020 at 18:29
  • 1
    \$\begingroup\$ Certainly somehow - e.g.. sorting the list by a key function which ranks invalid times to the right. But it'll probably cost a lot of bytes :( If I have a bug I don't know how to fix I delete my post and continue working on it (you'll be able to see the deleted post still and be able to make edits and then undelete when ready). \$\endgroup\$ May 22, 2020 at 18:56
4
\$\begingroup\$

Python 2, 142 bytes

lambda*t:'%02d'*2%max(g(-720,*t))[1:]
g=lambda w,h,m,t=10:w/720*[0]or[(w*w,h,m)]*({h/t-h%t,h%t-m/t,m/t-m%t}<={1,-1})+g(w+1,(h+m/59)%24,-~m%60)

Try it online!

without the strict output:

Python 2, 160 148 144 138 133 bytes

-6 11 bytes thanks to Surculose Sputum!

Input and output is hours, minutes.

lambda*t:max(g(-720,*t))[1:]
g=lambda w,h,m,t=10:w/720*[0]or[(w*w,h,m)]*({h/t-h%t,h%t-m/t,m/t-m%t}<={1,-1})+g(w+1,(h+m/59)%24,-~m%60)

Try it online!

\$\endgroup\$
6
  • 1
    \$\begingroup\$ min(map(S,(1,-1))) can be replaced with min(S(1),S(-1)) \$\endgroup\$ May 22, 2020 at 8:32
  • \$\begingroup\$ @SurculoseSputum thanks a lot \$\endgroup\$
    – ovs
    May 22, 2020 at 8:37
  • 1
    \$\begingroup\$ Saving 10 into a variable saves another byte. \$\endgroup\$ May 22, 2020 at 8:42
  • 1
    \$\begingroup\$ We can save another 2 bytes by allowing w to wrap around. \$\endgroup\$ May 22, 2020 at 9:06
  • 2
    \$\begingroup\$ -5 bytes by having w goes from -720 to 720 instead. \$\endgroup\$ May 22, 2020 at 16:34
3
\$\begingroup\$

JavaScript (Node.js), 108 bytes

t=>(g=i=>(k=0,s=(new Date(t-i*6e4)+0).slice(16,21),h(1)+h(3)+h(4)-3?g(~i+(i<0)):s))(0,h=l=>(s[k]-s[k=l])**2)

Try it online!

\$\endgroup\$
3
\$\begingroup\$

Python 2, 159 155 bytes

Heavily inspired by @Arnauld's JavaScript solution. Instead of converting the answer string from hex to base 6, I needed to convert it from hex base 36 to base 10, since Python does not have built-in conversion to an arbitrary base.

-4 bytes thanks to @ovs for the suggestion to convert from base 36

x=input()
j=0
while x>23:x-=[80,4,418,457,93,93,4,4,408,456,4,93,93,4,4,9][j/4]+7;j+=4
print`int("2T9DHQE9BIABWTC7VO76PFJ2QND3LLSQY0ISQTMZCHP5",36)`[j:j+4]

Try it online!

Takes input as an integer representing the time (eg. 2101 for 21:01, or 123 for 01:23). Output is a string in the form hhmm with no delimiter.

\$\endgroup\$
4
  • 2
    \$\begingroup\$ int("2T9DHQE9BIABWTC7VO76PFJ2QND3LLSQY0ISQTMZCHP5",36) is 4 bytes shorter than the hexadecimal representation. \$\endgroup\$
    – ovs
    May 22, 2020 at 8:41
  • 1
    \$\begingroup\$ Do oct save some bytes or waste or same? \$\endgroup\$
    – l4m2
    May 22, 2020 at 12:52
  • \$\begingroup\$ @l4m2 oct(0x4e504105105320820a28828a29a29c4414514534d14d34e34e5) would be 4 bytes longer than the base 36 to base 10 conversion \$\endgroup\$ May 22, 2020 at 17:04
  • \$\begingroup\$ @l4m2 base 36 and oct combined is still 2 bytes longer: oct(int("1L1MUOJXRSHFY2W81CIGKOUELDFBS0Y9PZCBMPD1",36))[j+1:j+5] \$\endgroup\$
    – ovs
    May 23, 2020 at 11:34
2
\$\begingroup\$

Jelly, 29 bytes

⁽¬ẊDŻ€Œpḣ⁽¢ẒðIAP€ẹ1ạÞiḢị⁸

A monadic Link accepting a list of four integers which yields a list of four integers.

Try it online! Or see the test-suite.

\$\endgroup\$
2
\$\begingroup\$

Charcoal, 59 bytes

≔⪪”)⊟ς∧Q@ςE⁶¿ê40ofυ_Y±F⌊M↨ ”⁴υ≔↔⁻↨⁶⁰I⪪S²Eυ↨⁶⁰I⪪ι²θ∧⊟υ§υ⌕θ⌊θ

Try it online! Link is to verbose version of code. I/O is as a 4-digit string without colon. Explanation:

≔⪪”)⊟ς∧Q@ςE⁶¿ê40ofυ_Y±F⌊M↨ ”⁴υ

Split a compressed string of all the possible times into substrings of 4 digits. (I tried computing the string but unfortunately that cost at least 6 more bytes.) Additionally include the value 2401 which will map to 0101 for the next day.

≔↔⁻↨⁶⁰I⪪S²Eυ↨⁶⁰I⪪ι²θ

Convert the input and the substrings into seconds since midnight and take the absolute difference.

∧⊟υ§υ⌕θ⌊θ

Print the substring with the least absolute difference, but remove the 2401 entry first, which causes the indexing to wrap around to 0101.

\$\endgroup\$
1
\$\begingroup\$

JavaScript (V8), 155 bytes

(s,o=Math.abs,m=k=>Math.min(o(s-k),o(s+2400-k)))=>[101,121,123,1010,1012,1210,1212,1232,1234,2101,2121,2123,2321,2323,2343,2345].sort((a,b)=>m(a)-m(b))[0]

Try it online!

\$\endgroup\$
1
\$\begingroup\$

APL+WIN, 106 bytes

Prompts for time as a vector of integers hh mm:

¯2↑¨'0',¨⍕¨,60 60⊤((|m)=⌊/|m←(¯15,1↓n)-60 60⊥⎕)/n←60 60⊥⍉17 2⍴¯33+⎕av⍳'7M!!!7!5***,,*,,,@,B5!555775777B7M'

Stores 16 possible times as characters in APL+WIN character set (extended ASCII) adjusted by 33 to make printable. Could be removed to save 4 bytes. Converts to seconds, gets closest match and outputs nested character vector of time. Unfortunately I cannot put this up on TIO because my usual route via Dyalog Classic does not work as Dyalog character set appears to be in a different order to APL+WIN. Anyone care to translate?

\$\endgroup\$
1
\$\begingroup\$

05AB1E, 35 bytes

60©Ž5—Ÿ®δвT‰JʒS¥ÄP}D®δβD14(ªI®β.xkè

Both input and output is a pair of [hours, minutes].

Try it online or verify all test cases.

Explanation:

60                 # Push 60
  ©                # Store it in variable `®` (without popping)
   Ž5—             # Push compressed integer 1425
      Ÿ            # Pop both, and push a list in the range [60,1425]
       ®δв         # Convert each integer to a base-60 list/pair
          T‰       # Add leading 0s by taking the divmod-10 on each inner integer,
            J      # and then joining the inner pairs together
ʒ                  # Filter this list by:
 S                 #  Convert it to a flattened list of digits
  ¥Ä               #  Get the absolute difference of each pair of digits
    P              #  And check if all of them are exactly 1
}D                 # After the filter: duplicate the list of remaining pairs
  ®δβ              # Convert each pair from a base-60 pair back to a base-10 integer
     D             # Duplicate that list of integers
      14(ª         # Append a trailing -14
          I        # Push the input-pair
           ®β      # Convert it from a base-60 pair also to a base-10 integer
             .x    # Get the integer in the list closest to this value
               k   # Get the index of this value in the duplicated list
                   # (which will result in -1 for the trailing -14)
                è  # And use that to index into the duplicated list of pairs
                   # (where the -1 will index into the last pair, which is the [23,45])
                   # (after which the result is output implicitly)

See this 05AB1E tip of mine (section How to compress large integer) to understand why Ž5— is 1425.

\$\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.