Forum Discussion

Patrick2788's avatar
Patrick2788
Silver Contributor
Oct 21, 2026

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

  • Patrick2788's avatar
    Patrick2788
    Silver 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
    
    ))))));

     

  • Patrick2788's avatar
    Patrick2788
    Silver 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

  • Terio's avatar
    Terio
    Brass 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.

  • I seem to have obtained the solution I set out to get; but not necessarily what was intended!

     

    • Patrick2788's avatar
      Patrick2788
      Silver 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.

  •  

    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!

     

  • Patrick2788's avatar
    Patrick2788
    Silver 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!

  • Terio's avatar
    Terio
    Brass 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

  • djclements's avatar
    djclements
    Silver 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... 👻

  • 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! 😀

  • djclements's avatar
    djclements
    Silver 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! 👻

    • Patrick2788's avatar
      Patrick2788
      Silver Contributor

      djclements​ 

      (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.

      Terio​ 

      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),

       

      • Terio's avatar
        Terio
        Brass 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




    • djclements's avatar
      djclements
      Silver 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.