Forum Discussion
Halloween 2026 Scheduling Challenge 🎃
Happy Halloween!
You'll find the rules in the screen capture below and in the attached workbook.
Creative solutions are encouraged. Feel free to use any functions available in the Beta channel.
I won't post my solution immediately so submissions aren't influenced.
Enjoy!
15 Replies
- Patrick2788Silver Contributor
This is my revised approach using SCAN. It's a more natural fit for this task than MAP because it's actually generating the queue, removing items out of stock, and keeping a running inventory. I decided to not include a "Cumulative" column.
/* [TBD] ------------------------------------------------------------------------- Function: RoundRobinλ Category: Algorithms (Scheduling) Author: Patrick H. Version: 1.1 Description: Generates a Round Robin schedule by supplying 1D arrays for process, arrival, and burst. Inputs are stacked into a 2D array and sorted by arrival time before scheduling begins. Processes are then serviced in Round Robin order using the specified quantum until all burst requirements are exhausted. Parameters: process - Process identifier (text) arrival - Ready time of each process (integer >= 0) burst - CPU time required for each process (integer > 0) quantum - Duration of each time slice (integer > 0) Notes: The function will halt in a few different scenarios: • #VALUE! - The heights of input arrays are not uniform (Arrays are not flattened) • #BURST! - Burst contains one or more text values or values <= 0. • #QUANTUM! - Quantum is text or a value <= 0. ------------------------------------------------------------------------------*/ RoundRobinλ= LAMBDA( process, arrival, burst, quantum, LET( // Validate inputs h₁, ROWS(process), h₂, ROWS(arrival), h₃, ROWS(burst), // Order by arrival input, SORT( HSTACK(process,arrival,burst),2), // Select 1D arrays process, TAKE(input,,1), burst, TAKE(input,,-1), // Scalarize quantum input quantum, INDEX(quantum,1,1), // Halting scenarios InValidInput?, OR(h₁ <> h₂,h₁ <> h₃), InvalidBurst?, OR(ISTEXT(burst),burst <= 0), InvalidQuantum?, OR(ISTEXT(quantum),quantum <= 0), IF(InValidInput?,#VALUE!, IF(InvalidBurst?,"#BURST!", IF(InvalidQuantum?,"#QUANTUM!", LET( // Label header, {"Process","Distributed","Remaining"}, // Total rounds needed for processing rounds, SEQUENCE( CEILING(MAX(burst) / quantum,1) ), // 2D array for SCAN initial, HSTACK(process,burst), Allocateλ, LAMBDA( nest,i, LET( // Remove items without quantities active, FILTER(nest,TAKE(nest,,-1) > 0), // 1D arrays process, TAKE(active,,1), qty, TAKE(active,,-1), // Allocate quantum, else return what remains distr, IF(quantum < qty,quantum,qty), // Running quantity remaining remaining, qty - distr, // Arrange as 2D array round, HSTACK(process,distr,remaining), round)), // Flatten, stack, and present flat, FLATTEN( SCAN(initial,rounds,Allocateλ) ), final, VSTACK(header,flat), final )))))); - Patrick2788Silver Contributor
I'm working on polishing another approach which uses SCAN to process the allocation in rounds.
The initial value is supplied as a 2D stack of Treats/Available.On each round, a check is done to remove items which are out of stock. The quantum (Items per turn) is subtracted from the available counts. It reads more naturally than the MAP solution.
The output is a n x 4 spill - Process/Distributed/Available/Cumulative
- TerioBrass Contributor
Patrick2788 wrote:
It's interesting with the 4-5 submissions so far there's been different approach for each one.
This is what makes (or used to make) forums interesting: the opportunity for people to exchange ideas while exploring the possibilities offered by Excel.
Today, AI is stifling creativity in favor of speed.
Bye. - PeterBartholomew1Silver Contributor
I seem to have obtained the solution I set out to get; but not necessarily what was intended!
- Patrick2788Silver Contributor
Thanks for sharing, Peter!
From what I've reviewed it looks like you've created a queue. I've stepped through a round of the calculations and evaluated everything to get a visual on what's going on:
With the endState lists being:
The interesting to me is that REDUCE is actually being used for reduction through processing the treats and performing the subtraction. MAP and MAKEARRAY can handle Round Robin, too, though REDUCE is a natural for handling what is essentially repeated subtraction through a queue. At least that's my read on what's happening.
It's interesting with the 4-5 submissions so far there's been different approach for each one.
- PeterBartholomew1Silver Contributor
This thread has picked up some discussion since I last looked!
My objective was to move the thinking from basic formulas towards the definition of compound data entities and associated operations, implemented as nested arrays and Lambda functions respectively ( I may address considerations such as efficiency later ).
=LET( n, SUM(ROUNDUP(Available / m, 0)), k, SEQUENCE(n), initialTreats, {HSTACK(Treat, Available)}, initialList, {HSTACK("Treat", "Number")}, initialState, HSTACK(initialTreats, initialList), results, REDUCE(initialState, k, DISTRIBUTEλ), return, TAKE(FLATTEN(results, "", 0), , -2), return )where the Lambda function is given by
DISTRIBUTEλ = LAMBDA(state▦, [_], // Beginning array of two nested arrays followed by a counter (unused) LET( // Separate treat stack and record list // Update Treats treats, FLATTEN(INDEX(state▦,1,1)), // An array showing both product names and counts item, INDEX(treats,1,1), // A scalar text string showing the next treat count, INDEX(treats,1,2), // A scalar numeric value showing its current count otherItems, DROP(treats, 1), // A 2D array of treats remaining, MAX(count - m, 0), // A scalar value endTreats, IF(remaining>0, VSTACK(otherItems, HSTACK(item, remaining)), otherItems), // This is a 2D array // Update List priorList, FLATTEN(INDEX(state▦,1,2)), newList, VSTACK( // This is the final record of an item and the remaining count priorList, HSTACK(item, count - remaining) ), // Combine to form end state endState▦, HSTACK({endTreats}, {newList}), // This is to be an array of two nested arrays endState▦ ) );Now, of course, another approach occurs to me. Instead of cycling the formulas each time a treat is given, I could cycle each time one runs through the entire treat basket!
For me though, I am at the stage of attempting to develop techniques, so this has already been a useful excercise!
- Patrick2788Silver Contributor
For this Round Robin challenge, I decided to iterate through process (treats), full slice counts 'k', and partial slices in 'mod'. MAP felt like the best tool for the task because the 1D shapes are the same.
Many Round Robin schedulers tend to build a queue. I chose to obtain the counts, expand the processes (treats), and assign to a queue by using Streakλ to obtain the appearance index. The only regret is not creating the entire schedule in one MAP. The next best thing was setting up SCAN for some easy accumulation.
/* [TBD] ------------------------------------------------------------------------- Function: RoundRobinλ Category: Algorithms (Scheduling) Author: Patrick H. Version: 1.1 Dependencies: Resizeλ, Streakλ Description: Produces a standard Round Robin schedule from process, arrival, burst, and quantum inputs. Each process receives equal-sized time slices (quantum) in cyclic order. If MOD(burst, quantum) <> 0, the remaining work is scheduled in a final tail slice. The ready-queue behavior is simulated structurally: processes are expanded into repeated quantum slices, assigned appearance indices via Streakλ, and sorted by appearance to emulate circular FIFO ordering. SCAN is used to accumulate slice boundaries, ensuring a non-overlapping, strictly increasing timeline. Parameters: process - Process identifier (text) arrival - Ready time of each process (integer >= 0) burst - CPU time required for each process (integer > 0) quantum - Duration of each time slice (integer > 0) ------------------------------------------------------------------------------*/ RoundRobinλ= LAMBDA( process, arrival, burst, quantum, LET( // Flatten, stack and sort inputs input, SORT(HSTACK(TOCOL(process),TOCOL(arrival),TOCOL(burst)),2), arrival, CHOOSECOLS(input,2), process, TAKE(input,,1), burst, TAKE(input,,-1), // Halting scenarios InValidInput?, OR(ISERROR(input)), InvalidBurst?, OR(OR(ISTEXT(burst)),OR(burst <= 0)), InvalidQuantum?, OR(ISTEXT(quantum),quantum <= 0), IF(InValidInput?,#VALUE!, IF(InvalidBurst?,"#BURST!", IF(InvalidQuantum?,"#QUANTUM!", LET( // Full slice counts k, QUOTIENT(+burst,quantum), // Partial slices mod, MOD(burst,quantum), // Unpack, nest processes, flatten Unpackλ, LAMBDA(p_,k_,m_, LET( low_stock, k_ = 0, each_process, EXPAND(p_,k_,,p_), time_slice, Resizeλ(HSTACK(0,quantum),k_), tail, HSTACK(p_,0,m_), sched, HSTACK(each_process,time_slice), final, IF(low_stock,tail, IF(m_,VSTACK(sched,tail),sched)), final)), flat, FLATTEN( MAP(process,k,mod,Unpackλ) ,,0), // Appearance index idx, Streakλ(TAKE(flat,,1)), // Sort by appearance sorted, SORTBY(flat,idx,1), // Burst accumulation acc, SCAN(0,TAKE(sorted,,-2),SUM), // Present header, {"Process","Start","Stop"}, final, VSTACK(header,HSTACK(TAKE(sorted,,1),acc)), final ))))));If you don't have access to version 3 compatibility, this is what the nested arrays look like before flattening:
Post FLATTEN:
SCAN can then process columns 2-3 from the above array to arrive at the start/stop for each round.
I really enjoy writing functions with nested array support. It feels like a weight had been lifted off my back! I re-wrote an amortization lambda the other day and couldn't believe how much easier it was to put together without those old limitations. My old recursive approach is obsolete! - TerioBrass Contributor
My version:
=LET( ∂,M3:M10-SEQUENCE(,ROUNDUP(MAX(M3:M10)/2,0),2,2), lst,TOCOL( IF( ∂>=-1, K3:K10&"🎃"&∂, ≠), 3, 1), candies,TEXTBEFORE(lst,"🎃"), qty,--TEXTAFTER(lst,"🎃"), run,SCAN( 0, 1+(qty>=0), SUM ), HSTACK( candies, VSTACK( 0, DROP(run,-1) ), run ) )The formula has made on italian Excel, and the translation maybe be not accurate, I attach the file to test it.
Bye - djclementsSilver Contributor
Version 2: broadcast and flatten
= LET( i, ROUNDUP(available / items_per_turn, 0), j, SEQUENCE( , MAX(i)), k, IFS(j < i, items_per_turn, j = i, 1 + MOD(available - 1, items_per_turn)), l, SCAN( , TOCOL(k, 2, 1), SUM), VSTACK({"Treat","Start","Stop"}, HSTACK(TOCOL(IF(k, treat), 2, 1), DROP(VSTACK(0, l), -1), l)) )I missed the obvious the first time around... 👻
- PeterBartholomew1Silver Contributor
I will have a go at this. My plan is to create two entities. The first is a dynamic table of the treats remaining at the end of each turn. The count is decremented and the specific treat moves to the foot of the list. The second entity is simply the list of treats handed out to date. This would be a REDUCE/VSTACK strategy that appends the latest treat to the prior list. Each entity is to be a nested list.
My objective is to explore a way of working; though it would be kind of nice to get a solution! 😀
- djclementsSilver Contributor
Struggling with this website... what a joke.
Trying again...
= LET( sch, SCAN( VALS3(HSTACK(available, treat)), SEQUENCE(SUM(ROUNDUP(available / items_per_turn, 0))), LAMBDA(acc,_, LET( arr, SORTBY(acc(1), TAKE(acc(1), , 1) = 0), bal, SINGLE(arr), amt, MIN(bal, items_per_turn), VALS3(SORTBY(IFNA(EXPAND(bal - amt, 2, 2), arr), rank = 1), amt, INDEX(arr, 1, 2)) ) ) ), end, SCAN( , MAP(sch, LAMBDA(val, val(2))), SUM), VSTACK({"Treat","Start","Stop"}, HSTACK(MAP(sch, LAMBDA(val, val(3))), DROP(VSTACK(0, end), -1), end)) )Where:
VALS3 = LAMBDA([value1],[value2],[value3], LAMBDA(x, CHOOSE(x, value1, value2, value3)))Note: SINGLE(arr) should automatically convert to "at"arr when the formula is committed to a cell. I used SINGLE because implicit intersection operators contained within a code block don't seem to be rendering properly in Microsoft Edge after posting.
This solution probably wouldn't perform all that great with a huge table/array of treats, due to the manipulation and indexing of the entire array at each iteration, but it works well for the dataset provided.
A step-through is available in the attached workbook to review the accumulated items for any given iteration (assuming the workbook is not stripped from my post again!).
Happy Halloween! 👻
- Patrick2788Silver Contributor
(first solution)
This is the slipping a thunk-through-a-keyhole method! For me it's not readily apparent what's going on until I sit down and really step through the calculations that I can appreciate the mental model being used. It takes a lot of skill to create 'sch' and not have it realized until you iterate over it with MAP and then you're pulled out what you need and assembling the result.
This is an interesting approach I've used in moving aggregation:
∂, M3:M10 - SEQUENCE(, ROUNDUP(MAX(M3:M10) / 2, 0), 2, 2),The only downside is creating and then discarding extra values when a treat is out of stock. Given the limitations of the current build (Nested arrays not yet in general release), it's a valid approach and the calculation times are generally good.
My favorite part is how you setup SCAN with an array of 2s and 1s. Very nice!
run, SCAN(0, 1 + (qty >= 0), SUM),- TerioBrass Contributor
Thanks Patrick2788 for you feedback.
Patrick2788 wrote:
Nested arrays not yet in general release
Indeed, not being able to use nested arrays, which is possible in GS, limits certain solutions; this should be overcome by the new capabilities introduced in the ßeta version.
With 3 at 2nd parameter in TOCOL I dropped the excess ones, thinking it was the best compromise.
Bye
- djclementsSilver Contributor
One small revision to minimize redundancy...
The second accumulated value at each iteration of sch can be changed from amt to amt + acc(2), so that end can simply be evaluated as MAP(sch, LAMBDA(val, val(2))).
I'm not about to edit my previous post, as it would most likely result in the workbook being stripped away.