21
\$\begingroup\$

You have probably seen these signs on the doors of various shops:

OPENING HOURS

mon-fri 0900-1800
sat-sun 1100-1530

The task here is to generate a sign like that, grouping consecutive days with the same opening hours, from a list of opening hours for the whole week. Note that the week "wraps around" for what is considered consecutive.

Input:

  • 7 elements, representing the opening hours for each day in a week, starting with Monday.
  • Each element is a string, on the form XXXX-XXXX
  • Example input:

    0900-1800 0900-1800 0930-1730 0930-1730 0900-1500 1100-1500 1100-1500
    
  • It's ok to send the input as an array (for instance as input to a function if you don't read from stdin)

Output:

  • A list of opening hours, where consecutive days with the same opening hours are shown as a range. Note that sunday (the last day) and monday (the first day) also are consecutive days.
  • A day where the day doesn't have similar opening hours to the days before or after is printed by itself
  • Days are specified as three lowercase letters: mon tue wed thu fri sat sun
  • Remember that first element in the input corresponds to mon, next to tue etc.
  • Opening hours are shown as in the input
  • Two examples

    mon-fri 0900-1800, sat-sun 1100-1500
    mon-wed 1030-1530, thu 100-1800, fri-sun 1200-1630
    
  • The output should be sorted, so ranges appear in the order the days do in the week. Monday is preferred to be first, but it may happen it is not first in a group because the week wraps. So in this case tue is the first range.

    tue-fri 0900-1800, sat-mon 1100-1500
    
  • Don't group unless consecutive, here wednesday and friday have same opening hours but are separated by a Thursday with different opening hours so they are listed by themselves.

    mon-tue 1000-1200, wed 0900-1500, thu 1000-1800, fri 0900-1500, sat-sun 1000-1500
    
  • The output can be either comma-separated as the examples here, or separated by a newline as in the example on top.

Test cases

First line is input, second line is expected output

0900-1800 0900-1800 0900-1800 0900-1800 0900-1800 1100-1500 1100-1500
mon-fri 0900-1800, sat-sun 1100-1500

0900-1800 0900-1800 0900-1800 0930-1700 0900-1800 1100-1500 1100-1500
mon-wed 0900-1800, thu 0930-1700, fri 0900-1800, sat-sun 1100-1500

1100-1500 0900-1800 0900-1800 0900-1800 0900-1800 1100-1500 1100-1500
tue-fri 0900-1800, sat-mon 1100-1500

1100-1500 1100-1500 0900-1800 0900-1800 0900-1800 0900-1800 1100-1500
wed-sat 0900-1800, sun-tue 1100-1500

1200-1500 1100-1500 0900-1800 0900-1800 0900-1800 0900-1800 1100-1500
mon 1200-1500, tue 1100-1500, wed-sat 0900-1800, sun 1100-1500

Rules

This is code-golf, so the shortest answer in bytes wins.

\$\endgroup\$
3
  • 7
    \$\begingroup\$ Welcome to Programming Puzzles and Code Golf. This is a good challenge; well done! In future, I would recommend using the sandbox before posting a challenge. (You know, just in case...) \$\endgroup\$
    – wizzwizz4
    Oct 8, 2016 at 12:55
  • 1
    \$\begingroup\$ Having Sunday and Monday as consecutive seems odd to me... \$\endgroup\$
    – Frédéric
    Oct 9, 2016 at 14:37
  • 1
    \$\begingroup\$ I feel like there should be a test-case for "mon-sun" just in case any submission doesn't properly handle that particular base case. \$\endgroup\$ Oct 9, 2016 at 18:14

5 Answers 5

7
\$\begingroup\$

JavaScript (ES6), 182 173 170 163 157 bytes

Saved 6 bytes with the help of edc65

Takes input as an array of strings and directly prints the result to the console:

h=>{for(D="montuewedthufrisatsun".match(/.../g),d=c=i=j=0;j<8;y=d)h[i]==h[6]?i++:(v=h[d=(i+j++)%7])!=c&&(j>1&&console.log(D[x]+(x-y?'-'+D[y]:''),c),x=d,c=v)}

Formatted and commented

h => {                               // input = list h of opening hours
  for(                               //
    D = "montuewedthufrisatsun"      // D = list of abbreviated names of days
        .match(/.../g),              //
    d =                              // d = current day of week
    c =                              // c = current opening hours
    i =                              // i = first day of week to process
    j = 0;                           // j = day counter
    j < 8;                           // stop when 7 days have been processed
    y = d                            // update y = last day of current day range
  )                                  //
  h[i] == h[6] ?                     // while the opening hours of day #i equal the opening
    i++                              // hours of sunday: increment i
  : (v = h[d = (i + j++) % 7]) != c  // else, if the new opening hours (v) of the current
    && (                             // day (d) doesn't match the current opening hours (c):
      j > 1 &&                       //   if this is not the first iteration:
        console.log(                 //     print:
          D[x] +                     //     - the first day of the current day range (x)
          (x - y ?                   //     - followed by either an empty string
            '-' + D[y]               //       or a '-' and the last day of the range
          : ''),                     //       (if they differ)
          c                          //     - the corresponding opening hours
        ),                           //   (endif)
      x = d,                         //   update x = first day of current day range
      c = v                          //   update c = current opening hours
    )                                // (endif)
}                                    // (end)

Test cases

let f =

h=>{for(D="montuewedthufrisatsun".match(/.../g),d=c=i=j=0;j<8;y=d)h[i]==h[6]?i++:(v=h[d=(i+j++)%7])!=c&&(j>1&&console.log(D[x]+(x-y?'-'+D[y]:''),c),x=d,c=v)}

f(["0900-1800", "0900-1800", "0900-1800", "0900-1800", "0900-1800", "1100-1500", "1100-1500"]);
console.log('-');
f(["0900-1800", "0900-1800", "0900-1800", "0930-1700", "0900-1800", "1100-1500", "1100-1500"]);
console.log('-');
f(["1100-1500", "0900-1800", "0900-1800", "0900-1800", "0900-1800", "1100-1500", "1100-1500"]);
console.log('-');
f(["1100-1500", "1100-1500", "0900-1800", "0900-1800", "0900-1800", "0900-1800", "1100-1500"]);
console.log('-');
f(["1200-1500", "1100-1500", "0900-1800", "0900-1800", "0900-1800", "0900-1800", "1100-1500"]);

\$\endgroup\$
3
  • \$\begingroup\$ D='montuewedthufrisatsun'.match(/.../g) using D as an array instead of a function should save some byte \$\endgroup\$
    – edc65
    Oct 9, 2016 at 15:10
  • \$\begingroup\$ @edc65 - Unfortunately D() can be called with -1 (when we look for 'the day before Monday'), which works with substr() but would not work with an array. \$\endgroup\$
    – Arnauld
    Oct 9, 2016 at 15:19
  • \$\begingroup\$ @edc65 - EDIT: by combining your method with another fix, this is definitely a win. Thanks! \$\endgroup\$
    – Arnauld
    Oct 9, 2016 at 15:43
3
\$\begingroup\$

Batch, 334 bytes

@echo off
set/af=l=w=0
set h=%7
call:l mon %1
call:l tue %2
call:l wed %3
call:l thu %4
call:l fri %5
call:l sat %6
call:l sun %7
if not %w%==0 set l=%w%
if %f%==0 set f=mon
call:l 0 0
exit/b
:l
if not %h%==%2 (
if %f%==0 (set w=%l%)else if %f%==%l% (echo %f% %h%)else echo %f%-%l% %h%
set f=%1
set h=%2
)
set l=%1

Takes input as command-line parameters, outputs each group on a separate line. Works by comparing each day's hours to the previous day, tracking f as the first day in the group, h as the hours for that group, l as the last day in the group and w for when the last group wraps back to the start of the week. When a mismatch is found the previous group is printed unless week wrapping is in effect. Finally when all the days are processed the last group is adjusted for any week wrapping and whether all the hours turned out to be the same before being output. 0 is used as a placeholder because empty strings cost more bytes to compare in Batch.

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

Jelly, 87 84 80 75 bytes

  • 4bytes with the kind help of @Dennis (fixed a hack I used with the ' quick, "flat")

Pretty sure there is a better way but for now:

Ṫ2ị$€;@µ+\µżṙ1’$Ṗị“ḅạṄMḤ9\E¥LḃɓṅÐĿ;$»s3¤Q€j€”-
ṙ7Ḷ¤Œr'€Ėµ2ịLµÐṂḢ
ÇṪḢ€ż@ÇÑ$G

TryiItOnline

How?

            “ḅạṄMḤ9\E¥LḃɓṅÐĿ;$» - compressed string "montuewedthufrisatsun"
                  v  v
Ṫ2ị$€;@µ+\µżṙ1’$Ṗị -- s3¤Q€j€”- - Link 1, getDayStrings: [offset, groupedList]
Ṫ                               - Tail: get groupedList
 2ị$€                           - second index of each: get group sizes, e.g. mon-thu is 4
     ;@                         - concatenate (reversed arguments): prepend with offset
       µ                        - monadic chain separation
        +\                      - cumulative reduce with addition: start indexes
          µ                     - monadic chain separation
           ż                    - zip with
               $                - last two links as a monad
            ṙ1                  -     rotated left by one
              ’                 -     decrement:  end indexes
                Ṗ               - pop: remove last one, it's circular
                 ị              - index into
                        ¤       - last two links as a nilad
                    --          -     compressed string of weekdays (as shown at the top)
                      s3        -     split into 3s
                        Q€      - unique items for each: [tue,tue] -> [tue]
                          j€    - join each with
                            ”-  - literal string "-"

ṙ7Ḷ¤Œr'€Ėµ2ịLµÐṂḢ - Link 2: CalculateGroupingData: list of time strings
   ¤              - last two links as a nilad
ṙ                 -    rotate time strings list left by
 7Ḷ               -    range 7: [0,1,2,3,4,5,6]
       €          - for each
    Œr            -     run length encode
      '           -     flat - i.e. don't vectorise
        Ė         - enumerate [[1,[groupsFromMon]],[2,[groupsFromTue], ...]
         µ        - monadic chain separation
              ÐṂ  - filter for minimum of
             L    -     length of
          2ị      -     item at index 2: The number of groupings
                Ḣ - head: get the first occurring minimal (i.e Mon, or Tue, ...)

ÇṪḢ€ż@ÇÑ$G - Main link: list of time strings
Ç          - call last link (1) as a monad: get the offset and grouping
 Ṫ         - tail: get the grouping
  Ḣ€       - head each: get the time information
        $  - last two links as a monad
      Ç    -      call last link (1) as a monad: get the offset and grouping
       Ñ   -      call the next link as a monad: get the day strings
    ż@     - zip (with reversed arguments)
         G - arrange as a group (performs a tabulation to nicely align the result)
  
\$\endgroup\$
1
\$\begingroup\$

JavaScript (ES6), 171 169 bytes

a=>a.map((e,d)=>(d='montuewedthufrisatsun'.substr(d*3,3),e!=h&&(f?o(f):w=l,f=d,h=e),l=d),f='',l=w='sun',h=a[6],o=f=>console.log((f==l?l:f+'-'+l)+' '+h))&&o(f||'mon',l=w)

Takes input as an array and outputs to the console on separate lines. This is almost exactly a port of my Batch answer; f now defaults to an empty string of course, while I can also default l and w to 'sun' (using a sentinel value saved me 3 bytes in Batch because I was able to merge the initialisation into the set/a).

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

BaCon, 514 496 455 bytes

The BASIC program below is shown with its indentation. But without the indentation it consists of 455 bytes.

The idea is to use the time schedules as indexes to an associative array. Then, each day represents a bit: Monday = bit 0, Tuesday = bit 1, Wednesday = bit 2 and so on. The actual values for the members of the associative array are calculated by the respective bits of the days using a binary OR.

After that, it is a matter of checking how many consecutive bits are present in the associative array members, starting with bit 0.

In case bit 0 and also bit 6 are set, there is a week wrap. In that case, start looking for the start of the next sequence of bits, memorizing this start position. Print the rest of the sequences, and as soon bit 6 is reached, the day range should be ended with the memorized position earlier.

LOCAL n$[]={"mon","tue","wed","thu","fri","sat","sun"}
GLOBAL p ASSOC int
SUB s(VAR t$ SIZE a)
    FOR i = 0 TO a-1
        p(t$[i])=p(t$[i])|BIT(i)
    NEXT
    LOOKUP p TO c$ SIZE o
    b=e=0
    REPEAT
        FOR i=0 TO o-1
            IF p(c$[i])&BIT(b) THEN
                IF b=0 AND p(c$[i])&65=65 THEN
                    WHILE p(c$[i])&BIT(b)
                        INCR b
                    WEND
                    e=b
                    BREAK
                FI
                ?n$[b];
                r=b
                REPEAT
                    INCR b
                UNTIL NOT(p(c$[i])&BIT(b))
                IF e AND b>6 THEN
                    ?"-",n$[e-1];
                ELIF b-r>1 THEN
                    ?"-",n$[b-1];
                FI
                ?" ",c$[i]
            FI
        NEXT
    UNTIL b>6
    FREE p
ENDSUB

Using the following calls to invoke the SUB:

s("0900-1800", "0900-1800", "0900-1800", "0900-1800", "0900-1800", "1100-1500", "1100-1500")
PRINT "======"
s("0900-1800", "0900-1800", "0900-1800", "0930-1700", "0900-1800", "1100-1500", "1100-1500")
PRINT "======"
s("1100-1500", "0900-1800", "0900-1800", "0900-1800", "0900-1800", "1100-1500", "1100-1500")
PRINT "======"
s("1100-1500", "1100-1500", "0900-1800", "0900-1800", "0900-1800", "0900-1800", "1100-1500")
PRINT "======"
s("1200-1500", "1100-1500", "0900-1800", "0900-1800", "0900-1800", "0900-1800", "1100-1500")

Output:

mon-fri 0900-1800
sat-sun 1100-1500
======
mon-wed 0900-1800
thu 0930-1700
fri 0900-1800
sat-sun 1100-1500
======
tue-fri 0900-1800
sat-mon 1100-1500
======
wed-sat 0900-1800
sun-tue 1100-1500
======
mon 1200-1500
tue 1100-1500
wed-sat 0900-1800
sun 1100-1500
\$\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.