Forum Discussion

Patrick2788's avatar
Patrick2788
Silver Contributor
Jun 25, 2022

A LAMBDA Exercise

For this exercise I stripped down a calendar template and put in some sample data.  The goal is to obtain gross pay for July for 3 employees.

 

The data arrangement:

I believe there are several solutions and I welcome them all.  My first take was using REDUCE but I thought I could be more efficient with another approach. My solution is included in the workbook.  I may revisit the REDUCE solution after I've put some more thought into it.

 

Formulas only please (No fill handle). No VBA.

I'll mention a few who people might be interested in this exercise. All are welcome.

PeterBartholomew1 mtarler Lorenzo OliverScheurich 

 

 

  • Patrick2788 

    As variant

    weekdayMultiplier=
    LAMBDA( weekday, CHOOSE(weekday, 1.5, 1, 1, 1, 1, 1, 1.5) );
    
    weekdayHours= 
    LAMBDA( vector, weekday,
        SCAN(0, vector,
            LAMBDA(a,v,
                IF( v= 4, 16, // with simplified holidays
                IF( v = "", 0,
                IF( ISTEXT(v), a, 8*weekdayMultiplier(weekday)) ) ) ) )
    );
    weekdayPersonHours=
    LAMBDA( vector, weekday, name,
        SUM( weekdayHours(vector, weekday)*(vector=name) ));
    
    monthPersonHours=
    LAMBDA( month, name,
            REDUCE(0, SEQUENCE(7), LAMBDA( a,v,
                a + weekdayPersonHours( CHOOSECOLS( month,v), v, name) ))
    );
    
    GrossPay=
    LAMBDA( month, names, rates,
        SCAN(0, names, LAMBDA(a,v, monthPersonHours(month, v) ) )*
        rates
    );
    
  • mtarler's avatar
    mtarler
    Silver Contributor

    Patrick2788 Maybe I'm missing some of the challenge but I didn't use Lambda:

    =(1.5*COUNTIFS(july,staff)-0.5*COUNTIFS(WkDays,staff)+COUNTIFS(holiday,staff))*8*rate

    I added 2 Names: WkDays = the range of the month M-F and holiday = July 4

     

  • Patrick2788 

    I was undecided whether to delicately unwrap the calendar format into meaningful arrays or to simply hit it with a blunt instrument, COUNTIFS in the present case.  I came to the conclusion that the latter was sufficient, provided I was allowed to mark the holiday as a named range.

    Worksheet formula
    = rate * MAP(staff, Hoursλ);
    
    Hoursλ = LAMBDA(s,
        8 * SUM(COUNTIFS(holiday, s), dayRate * BYCOL(july, LAMBDA(day, COUNTIFS(day, s))))
    );
    
    dayRate = {1.5, 1, 1, 1, 1, 1, 1.5};

    One thing that I would observe about Patrick's original solution, is that 'hours()' is a Lambda function and therefore will accept parameters directly from the helper functions without the use of an additional Lambda to pass parameters.  I only realised that once I had reduced my own formula by naming its Lambda function to be "Hoursλ".

     

  • Patrick2788's avatar
    Patrick2788
    Silver Contributor

    Patrick2788 

    I think I made this exercise too easy.  mtarler struck it with a blunt instrument (COUNTIFS) and got the job done.  PeterBartholomew1 - I thought I made this one a pain with the holiday pay but you seem to have resolved this one easily working COUNTIFS into your LAMBDA.

    I believe SergeiBaklan has a solution that can be scaled to include 3D referencing.

     

    I had to do it. I've updated the workbook, so it now includes 3 months.  I've made some design changes: the number for the day of the week is now a true date and a list of holidays is included in the summary sheet.

  • Patrick2788 

    This is not an independent solution since I started of by examining SergeiBaklan's solution and only then started to play.  The initial manipulation of the 3D Range is truly amazing, I am only beginning to get to grips with some of the new array shaping functions.

     

    I liked Sergei's use of nested Lambda functions; I am moving towards using such functions wherever possible rather than wherever necessary to improve the structure and self-documenting character of solutions, but I have not got there yet!  I did think the use of SCAN to multiply the hourly rates was something of overkill though.

     

    My formulas were

     

     

    Worksheet formula
    = rate * MAP(staff, BillableHoursλ);
    
    BillableHoursλ=
    LAMBDA(name,
        LET(
            stack,      SORT(WRAPROWS(TOCOL(calendar, , 1), 3)),
            dates,      TAKE(stack,,1),
            holiday?,   COUNTIFS(holidays,dates),
            weekend?,   WEEKDAY(dates,2)>5,
            time,       1 + 0.5*weekend? + holiday?,
            staff,      TAKE(stack,,-2),
            onshift?,   BYROW(staff,LAMBDA(s,OR(s=name))),
            SUM( IF(onshift?, 8*time) )
        )
    );

     

     

  • Patrick2788 

    I have used your challenge as a further exercise.  I note that the formula that Sergei introduced to restructure the 3D Range, that contains the calendar, is recalculated in its entirety once for each staff member.  Provided the calculation is fast that is not a problem, but out of interest I set out to limit the calculation step to a single occurrence.  That meant I could not use a defined Name or a Thunk to pass the restructured array 'stack'.

     

    I could create a monolithic block of code and place the calculation of 'stack' before the MAP that determines the eligible hours for each staff member.  To move away from such monolithic code, I wanted to use a further nested Lambda function.  This could either be defined within the outer Lambda function by using LET, in which case it would be within scope of the formula-local name, or 'stack' itself could be passed as a parameter.  I implemented each, but chose the second option.

    GrossPayλ 
    = LAMBDA(calendar, names, rate,
        LET(
            stack, SORT(WRAPROWS(TOCOL(calendar, , 1), 3)),
            rate * MAP(staff, EquivalentHoursλ(stack))
        )
    );
    
    EquivalentHoursλ 
    = LAMBDA(stack,
        LAMBDA(name,
            LET(
                dates,    TAKE(stack, , 1),
                holiday?, COUNTIFS(holidays, dates),
                weekend?, WEEKDAY(dates, 2) > 5,
                time,     1 + 0.5 * weekend? + holiday?,
                assigned, TAKE(stack, , -2),
                onshift?, BYROW(assigned, LAMBDA(s, OR(s = name))),
                SUM(IF(onshift?, 8 * time))
            )
        )
    );

    In order to pass the additional parameter into the MAP construct I needed to define the function 'EquivalentHoursλ' in a Curried form, passing one parameter at a time by using multiple nested LAMBDAs.

     

    I am sorry if this reads like complete gobbledegook; I have tried my best to express the ideas clearly, but I am not convinced I have succeeded!  

    • Patrick2788's avatar
      Patrick2788
      Silver Contributor
      The more I study stacking (and unstacking), the more I believe there's a need for a function capable of determining the position of an element in an array (Not a range but an array). The other day someone had requested a formula to 'un-stack' a range. Worked on it bit (Explored pulling row and column with MAP from a mirrored range) but it kept coming back to finding the position within in the array. I saw L Z's tidy solution and moved on.
      https://techcommunity.microsoft.com/t5/excel/how-to-build-a-destacked-table/m-p/3558426

      ROW and COLUMN do not work on arrays and there's only so much XMATCH can do as arrays get larger and it won't calculate. I believe SCAN can sometimes get me the element position but it seems like a lot of heavy lifting.

      Maybe the function would be called ELEMENT and would return position number within a vertical or horizontal array,
      • PeterBartholomew1's avatar
        PeterBartholomew1
        Silver Contributor

        Patrick2788 

        Hi Patrick

        I am not so sure.  If anything, there is less of a need, given the new array shaping functions, for formulas relating indices in order to generate array transformations.  I followed your link and posted a formula solution.  It required 200ms to generate a 50,000 x 12 array of results.

         

        The question of locating an element of an array is a little unusual to my mind.  More often it is a case of mapping an entire array to a new layout, though one could conceivably play 'find the "X"'.  One thing I have done to get a handle on some of the new array shaping functions is to apply them to a data set but, in parallel, to apply the same formula to an equivalent index array. 

         

        Then again, maybe this isn't what you are trying to achieve!!

         

Resources