# Visually arrange multi-day events on a calendar

### The arrangement algorithm

I think I've determined the algorithm that Google Calendar uses to visually arrange day-long and longer events at the top of the "Week" view (you may have an example here especially if you have a shared workplace calendar) and it seems to be:

• Sort all events by starting date (earlier first); if same starting date, sort them by length (longer first); if same starting date and length, sort them by insertion time (earlier first)
• For each element in this sorted list, add it to the first existing horizontal swimlane that has space for it or, if there isn't a suitable one, create a new swimlane at the bottom and place it there

Note that the result is not incremental. I.e., adding a new event may rearrange existing events.

An example result is the following image, where the events have been inserted in alphabetical order.

Transcript:

First row: event "a" Monday to Friday (inclusive), event "k" Saturday

Second row: event "j" Monday to Wednesday, event "c" Thursday to Sunday

Third row: event "i" Monday to Tuesday, event "b" Wednesday to Thursday, event "f" Friday to Sunday

Fourth row: event "l" Tuesday to Wednesday, event "d" Thursday to Friday

Fifth row: event "e" Wednesday to Thursday, event "g" Friday to Sunday

Sixth row: event "h" Friday to Sunday

(As a European, I use the convention that the week starts on a Monday. You're free to read my examples as starting on a Sunday: it's irrelevant once you pick an encoding.)

### The challenge

Write the shortest code (program or function) that generates and displays the Google-style arrangement of a list of events.

The algorithm as described may have equivalent formulations, possibly simpler ones. Just make sure that your algorithm would produce the same arrangements as the reference algorithm.

a list of events in a convenient and reasonable form of your choice (e.g. a list of pairs (a, b) where a ∈ {0...6} is the starting day of the week, b ∈ {0...6} is the end day or the length of the event).

• Events don't have a name in the input.
• Order matters.
• There are up to 26 events. They are all entirely contained within the week.
• The input is assumed to be valid, i.e. if you choose a (start day, end day) format you'll know that end ≥ start. All events last a whole number of days, i.e. not zero.
• Use any valid input (stdin, function arguments, read from file, Google Sheets cell, ...).

an ascii-art or graphical output of the arranged events.

• Events must be labelled with a distinctive name (e.g. "a" to "z") or a distinctive color (e.g. a discrete sampling of a color strip). If your language is designed for 16-color displays, it's ok to wrap (i.e. first and seventeenth event share the same color). You can use text labels instead of color labels even if your code supports colors.
• The distinctive name/color must have an implicit order and the order must match the order of the events in the input.
• Output can be as described above (i.e. the week goes left to right and the swimlanes are arranged from top to bottom) or transposed (the week goes top to bottom and the swimlanes are arranged left to right).
• Output must be explicit, i.e. print to console or display on screen. Returning a 2D matrix of chars is not in the spirit of this challenge. Returning a whole printable string including its own newline chars is ok.

Example ascii-arts:

AAAAAK   |   [=====A======].K]...  |   A____K'
JJJCCC   |   [==J===].[==C===]...  |   J__C__'
IIBBFFF  or  [=I=].[=B=].[==F===]  or  I_B_F__
LLDD    |   ...[=L=].[=D=]......  |   'L_D_''
EEGGG  |   ......[=E=].[==G===]  |   ''E_G__
HHH  |   ............[==H===]  |   ''''H__

( mm tt ww tt ff ss ss )


#### Examples

Here is a reference implementation with some examples.

❕ Make sure to try some of these examples, because my illustration above doesn't cover all corner cases (e.g., when there are no events on a Monday, or when a swimlane has some gaps within itself).

#### Scoring

This is , but I'd like to encourage (including colored text output) over monochromatic , so (including colored text) responses get a 2/3 multiplier.

• I wouldn't recommend trying to do both graphical-output and ascii art in the same challenge, as they're completely separate problems and bonuses are generally discouraged anyway due to requiring the answerer to write multiple versions of one piece of code to determine what's shortest. For this challenge in particular, the core of the challenge is laying out the events in a grid, and asking for vaguely-specified graphical output is just adding another complication. May 1 at 9:45
• @emanresuA I see your point and I had considered it before posting. I decided to post it in this form anyway because I'm willing to see if "asking for poorly-specified graphical output" may encourage creativity rather than adding complication (since it's a removal of constraints rather than an addition). I accept that I may be proven wrong. May 1 at 9:48
• "you'll know that end ≥ start. All events last a whole number of days, i.e. not zero." These two sentences kinda contradict each other. Shouldn't it be end > start instead of end start, if the events can't last zero days? May 1 at 9:53
• @KevinCruijssen In the particular encoding I've used in my examples, that the answerer needn't follow, start==end means that the event lasts one whole day (start day = Monday, end day = Monday --> event runs through all of Monday). In fact, I added the "whole, i.e. not zero" indication precisely to make it clear that end==start doesn't imply length=0. May 1 at 9:57
• (I see why this can be annoying if it only means more boilerplate, and why there are meta posts recommending against such rules. Hint taken for the next challenge! But I wouldn't change it now, that the problem has been live for a few hours. Thanks for the feedback.) May 1 at 14:24

# Python, 166 bytes × 2/3 = 110.666

def f(s,l,c=range(1,99)):g=0;print();f(*zip(*[(i,-j,k)for i,j,k in sorted(zip(s,map(int.__neg__,l),c))if g>i or[print(end=' '*(i-g)+f'[38;5;{k*5}m#'*-j),g:=i-j]*0]))


Attempt This Online! (Not the best experience since ATO can't display the colors)

A recursive function that accepts two lists as parameters, where each element in the first list represents the starting day of an event, and the corresponding element in the second list specifies the duration of that event.

Output:

# Python, 156 bytes

def f(s,l,c=range(65,91)):g=0;print();f(*zip(*[(i,-j,k)for i,j,k in sorted(zip(s,map(int.__neg__,l),c))if g>i or[print(end=' '*(i-g)+chr(k)*-j),g:=i-j]*0]))


Attempt This Online!

Pretty much the same as above, but displays with monochromatic ascii-art. with events labeled with uppercase letters.

# JavaScript (ES6), 235 × 2/3 = 156.67 bytes

-5 thanks to @Mukundan314

Expects a list of [start, end] pairs and prints using ANSI colors.

a=>a.map((a,i)=>[...a,[38;5;\${i*5+3}m@]).sort(([s,e],[S,E])=>-(s<S|s==S&e>E)).map(([s,e,c])=>(g=(C,x=e)=>x<s||C(x)*g(C,x-1),o.some(r=>g(x=>!(R=r)[x]))||o.push(R=[]),g(x=>R[x]=c)),o=[])&&o.map(r=>[...r].map(c=>c||" ").join).join



Try it online! (ASCII gibberish without color rendering)

Or try a monochromatic version using A..Z.

### Output

• @NicolaSap I no longer dare to say that it's now OK ... but maybe it is. May 1 at 20:01
• :) :) I haven't checked it in depth but nothing jumps out at me as wrong from looking at the screenshot May 1 at 20:19

# Charcoal, 41 bytes

Ｗ⁻Ｅθ⟦§κ⁰±§κ¹§αλ⟧υ⊞υ⌊ιＦυ«Ｊ§ι⁰¦⁰ＷＫＫＭ↓×⊟ι±⊟ι


Try it online! Link is to verbose version of code. Takes input as a list of [start, length] pairs. Explanation:

Ｗ⁻Ｅθ⟦§κ⁰±§κ¹§αλ⟧υ⊞υ⌊ι


Create a list of modified events consisting of the start, negated length and letter, and sort that list.

Ｆυ«


Loop over the sorted events.

Ｊ§ι⁰ζ


Try placing the event on the first swimlane.

ＷＫＫＭ↓


Move down until a free swimlane is found.

×⊟ι±⊟ι


Output the event.

38 bytes using the newer version of Charcoal on ATO and @JonathanAllen's input format:

Ｗ⁻Ｅθ±⟦κλ⟧υ⊞υ⌈ιＦ±υ«Ｊ⌊§ι⁰¦⁰ＷＫＫＭ↓×§α⊟ιＬ⊟ι


Attempt This Online! Link is to verbose version of code.

# Jelly, 32 bytes

o⁼+ɗ€TḢ
żJNÞṚṬa¥/€oç¦ṭ@ç?ƒ-ḊịØWY


A monadic Link that accepts a list of Events, each as an ordered list of positive integers (e.g. Mon-Sun = [1,2,3,4,5,6,7]) and yields a list of characters with _ for any unfilled space on the left of a swimlane then A-Z for event days (trailing unfilled space is omitted) and newlines between swimlanes.

Try it online! Or see the test-suite (translates from reference implementation inputs).

Note:
A coloured version using ANSI escape codes comes in at 53 bytes, more than $$\\frac{3}{2}\$$ the size, TIO.

#### How?

o⁼+ɗ€TḢ - Link 1, Find available index: SwimlanesSoFar; EventMask
o       -   {Swimlane} logical OR {EventMask} (vectorises)
T  - truthy indices (1-indexed)

żJNÞṚṬa¥/€oç¦ṭ@ç?ƒ-ḊịØWY - Link: list of Events (lists of day numbers)
żJ                       - zip with [1..length(Events)] -> [[Event, ID],...]
NÞ                     - sort by negated values
Ṛ                    - reverse
¥/€               - reduce each by:
Ṭ                   -   untruth {Days}
a                  -   logical AND {ID} -> EventMask
e.g. ID 9, Thu-Fri = [0,0,0,9,9]
ƒ-      - starting with -1 reduce by:
?        -   if...
ç         -   ...condition: call Link 1
oç¦            -   ...then: logical OR at index given by Link 1
ṭ@          -   ...else: append
Ḋ     - dequeue (remove the -1)
ịØW  - index into "ABC...Zabc...x012..9_"
Y - join with newlines