Forum Discussion
FIFO Inventory Formula Challenge
To revel my second anniversary as a member of this EXCELlent Community, I hereby proffer this challenge to season your array manipulation skills. Perhaps, keenadvice mathetes Riny_van_Eekelen PeterBartholomew1 SergeiBaklan and lori_m could again spice our learning experience!
- Riny_van_EekelenPlatinum Contributor
Twifoo Don't really now what "Array manipulation" is and I'm probably violating all the rules of the challenge. Sorry about that. I used your challenge to play around with LET, in stead.
- mathetesSilver Contributor
Well done Riny_van_Eekelen !
I''m still working on it (in my head mostly)...later on today I will be home where I have access to two more reasonable screens, rather than just a small MacBook Air.... that should help.
It would be nice if one of us--it won't be me--were to come up with a LAMBDA based solution.
- TwifooSilver Contributor
I would define array manipulation as the skillful control of formula results. Nonetheless, the challenge is to derive the correct results, regardless of the functions used thereby.
- mathetesSilver Contributor
OK, if permitted, I'm going to withdraw my resignation from the match. I stuck it out after all (one thing that helped a lot: I learned how to do line breaks in the Formula Bar, which makes it a lot more readable, as all of you know). Here's the formula:
=LET(
PQt,FILTER(TrnQty,(ProductName=J18)*(TrnType="Purchase")),
SQt,FILTER(TrnQty,(ProductName=J18)*(TrnType="Sale")),
NQt,SUM(PQt)-SUM(SQt),
LPuR,COUNT(PQt),
ARQtPr,FILTER(TrnQty:TrnPrice,(ProductName=J18)*(TrnType="Purchase")),
LPuQ,MIN(NQt,INDEX(ARQtPr,LPuR,1)),
LPuP,MIN(NQt,INDEX(ARQtPr,LPuR,2)),
SLPuQ,MIN(NQt-LPuQ,INDEX(ARQtPr,LPuR-1,1)),
SLPuP,MIN(NQt,INDEX(ARQtPr,LPuR-1,2)),(LPuQ*LPuP)+(SLPuQ*SLPuP))
I could not make SUMPRODUCT work with the last four "name values" -- any idea why?
- TraderbearCopper Contributor
I have applied your Let algorithm to a FIFO calculation for option trading. I have been able to get it to work--partially. To use it this way, I had to allow for negative inventory (equity) values. There are cases where it gives the wrong answer. If you are interested in helping me find the problem, I can send you a spreadsheet that shows the problem.
Thanks,
Paul Accampo
How do you generate these exercises? Amazing!
Here I again didn't understand the logic behind (From Last Purchase Qty), just imitate your formula. First attempt
=LET( type, -(EVEN(TrnType="Sale")-1), prod, (ProductName=J18), table, CHOOSE({1,2,3,4},TrnNum,TrnQty*type, TrnPrice, type*prod), total, SUM(INDEX(FILTER(table,prod),0,2)), two, INDEX(SORT(FILTER(table, prod*type=1),,-1),{1;2},{2,3}), price, INDEX(two,,2), lastQty, MIN(total, INDEX(two,1,1)), QTYs, CHOOSE({1,2}, lastQty, total-lastQty), MMULT(QTYs,price) )
If logic will be more clear perhaps will do another attempt in few days.
- TwifooSilver Contributor
Let me clarify the logic of the challenge, as follows:
1. Calculate the unsold quantity of the product by subtracting the sold quantity from the purchased quantity. For "Courage", the purchased quantity of 2,611 units minus the sold quantity of 2,430 units equals the unsold quantity of 181 units. Absent any contrary evidence of loss, the unsold quantity must be equal to the physical count.
2. Determine the quantity of the product from the last purchase. For "Courage", such quantity is 101 units from Transaction 31 at 76.00 per unit.
3. Determine the lower value between the physical count and the quantity from the last purchase. For "Courage", the lower value between the physical count of 181 units and the quantity from the last purchase of 101 units is obviously the latter. Thus, of the 181 units counted physically, 101 units originate from the last purchase of 101 units from Transaction 31 at 76.00 per unit while the remaining 80 units originate from the second to the last purchase, which is 760 units from Transaction 22 at 65.00 per unit.
For "Integrity", the lower value between the physical count of 170 units and the quantity from the last purchase of 550 units is obviously the former. Thus, the entire 170 units originate from only the last purchase of 550 units from Transaction 40 at 20.00 per unit.
4. Determine the value of the physically counted inventory under the FIFO method by multiplying the dissected units by their corresponding purchase prices. For "Courage", (101 x 76.00) + (80 x 65.00) = 12,876.00.
For "Integrity", (170 x 20.00) = 3,400.00.
Amazed by how I generate these formula exercises? These exercises are real business circumstances included in my textbooks on EXCELlent Financial Accounting and Reporting!
- PeterBartholomew1Silver Contributor
Robert, I was going to apologise for using your challenges to explore the developing product that is Excel and learning new techniques rather than addressing the terms of the challenge head on. It appears that I may not be alone in this!
I did even consider using recursion to keep a running total of stock value transaction by transaction but came to the conclusion that it would be too heavy in terms of its computation and would represent overkill. I also wondered whether there is opportunity for a greater level of nested Lambda functions to expose the overarching logic to a greater extent but getting my brain to shift gears is not coming easily! I had a look at some theory but the function representing recursion
λ(λx.x x) (λx.x x)
does not come easily to me.
- PeterBartholomew1Silver Contributor
I have had the audacity to work on SergeiBaklan 's version of the solution. My reason for doing this was that I wanted to see how easy (or difficult) collaborative development might be. My first finding was that a utility that steps through the local names within a LET function returning their values one by one would be of immense value. The second conclusion is that working with someone else's LET formula is, indeed, easier than working with traditional formulas (such as we were meant to create for the present challenge).
I did make a change to Sergei's formula in that I replaced the matrix multiplication MMULT by a simpler SUM. To take the work forward I then wrapped the LET with LAMBDA to improve the parameter passing (I detest direct cell references in general and was never going to find one buried in the middle of a multi-line function appealing, despite its being appropriate to the challenge).
= LAMBDA(prNm, LET( type, -(EVEN(TrnType="Sale")-1), prod, (ProductName=prNm), table, CHOOSE({1,2,3,4},TrnNum,TrnQty*type, TrnPrice, type*prod), total, SUM(INDEX(FILTER(table,prod),0,2)), two, INDEX(SORT(FILTER(table, prod*type=1),,-1),{1;2},{2,3}), price, INDEX(two,,2), lastQty, MIN(total, INDEX(two,1,1)), QTYs, CHOOSE({1;2}, lastQty, total-lastQty), SUM(QTYs*price)) )(@Product)
I have yet to clarify thoughts on how the solutions to such problems might best be packaged. One option might be to output the results to a table and create a rich data type with inventory quantities and values as attributes. Another might be to use recursion to generate values for the five product lines as a single array.
I also look forward to Twifoo 's solution in due course. It does me no harm to be shown that amazing solutions can be created with traditional techniques.
- TwifooSilver ContributorI'm already accustomed to your inherent aversion of using direct cell references. Even so, I'm impressed at your improvement on the solution of SergeiBaklan .
As I had previously advocated, traditional formulas are accessible to more users than modern Excel functions. For that reason, my formula challenges reach the widest possible audience, rather than a privileged few.- tbouldenIron Contributor
TwifooDeleted previous reply, getting used to the set-up on the forum. I've used LET below for readability and in the first cell in the attached, but left other formulas using older functions. On the right track?
=LET(
Product,"Courage",
ProdFilter,(ProductName=Product),
PurchFilter,(TrnType="Purchase"),
InventoryOnHand,SUM(CHOOSE(MATCH(TrnType,{"Purchase";"Sale"},0),1,-1)*ProdFilter*TrnQty),
LastPurchasePrice,INDEX(TrnPrice,MAX(TrnNum*PurchFilter*ProdFilter)),
LastPurchaseQty,INDEX(TrnQty,MAX(TrnNum*PurchFilter*ProdFilter)),
LastPurchaseInv,MIN(InventoryOnHand,LastPurchaseQty),
PrevPurchasePrice,INDEX(TrnPrice,LARGE(TrnNum*PurchFilter*ProdFilter,2)),
PrevPurchInv,(InventoryOnHand-LastPurchaseInv),
LastPurchaseInv*LastPurchasePrice+PrevPurchInv*PrevPurchasePrice)
- lori_mSteel Contributor
One more... was trying to save characters, could be made clearer by using longer names:
=LET( T,TrnType="Purchase", P,ProductName=J18, B,SORTBY(IF({1,0},TrnQty*-P*-1^T,TrnPrice),TrnType,1,TrnNum*P,-1), S,SUM(INDEX(B,,1)), MMULT(MIN(S,INDEX(B,1,1))*{1,-1}+{0,1}*S,INDEX(B,{1;2},2)) )
and a slightly shorter one based on the sample file:
=LET( T,TrnType="Purchase", P,ProductName=J18, S,SUM(-1^T*-P*TrnQty), MMULT(MIN(S,LOOKUP(2,1/T/P,TrnQty))*{1,-1}+{0,1}*S, INDEX(TrnPrice,AGGREGATE(14,6,TrnNum/T/P,{1;2}))))
I couldn't think of any nice ways to do this in old style formulas, interested to see your formula...
- keenadviceBrass Contributor
Could someone explain me what is the benefit of using in LET() as short names as possible ? IMHO, that only adds more headache both for debugging and maintenance stage.
- PeterBartholomew1Silver Contributor
Hi Lori
It shouldn't take you more than a few minutes to write a recursive Python script to reduce any LET function back to its simplest form (grin)! Whether the resulting formula is 'nice' or not is a matter of taste. Like beauty, simplicity is in the eye of the beholder. What others see as simplicity I see as primitive and unstructured, i.e. a classic demonstration of complexity!
BTW I reported the behaviour of my recursive formula that produced an array of results for the 5 products to Microsoft as an error. Oddly, although I could correctly output the array, I could not then count or sum the values it held (the start of the problem was that the 5 values were represented within 1 row?
- lori_mSteel Contributor
Agree discussions around simplicity / complexity, etc. are pretty pointless given their subjective (and recursive!) nature. I was thinking of 'Nice' in a 'Pythonic' sense and it does seem Excel is heading in the direction of such languages.
I don't have access to LAMBDA to test but it should be possible to use recursion within a LET statement by doing something along the lines of this Python code:
Y = lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args))) fib = lambda f: lambda n: (n if n<2 else f(n-1)+f(n-2)) >>> list(map(Y(fib),range(10))) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
- keenadviceBrass Contributor
Fully accepting that I am too late to the party here...
...(and desperately trying not to look too hard at others' solutions in an attempt to see what I come up with that's either different or congruent)...
...how does this solution (at 231 characters) do Twifoo ?
=LET(
Crit,
(TrnType="Purchase")*(ProductName=J18),
LPQ,
INDEX(SORT(FILTER(TrnQty,Crit)),1),
PC,
SUM(Crit*TrnQty)-
SUM((TrnType="Sale")*(ProductName=J18)*TrnQty),
SUM(INDEX(SORT(Crit*TrnPrice,,-1),{1,2})*
CHOOSE({1,2},LPQ,PC-LPQ))) - mathetesSilver Contributor
Oh, my! It's an honor to be included among such august company, an honor I fear I don't deserve at all. I'm here to learn, and occasionally offer some more basic suggestions.
But, challenged, I'll give it a try later today.
- mathetesSilver Contributor
Well, guys, if this were a chess match (which, metaphorically, it is) I would tip my king on its side, and reach out to shake your hands.
I've truly had fun learning how to nest FILTER within INDEX and COUNT (among others), have formulas that will get each of the salient numbers here, and could write a LET to define all the re-used array formulations, but my head is starting to feel as it it might explode just from contemplating putting them all into a single formula, as Robert has stipulated, to say nothing of actually doing it.
So it's been a learning experience for sure, and I look forward to applying my learnings. I'll keep looking over your shoulders in months ahead. I AM going to spend some time studying each of your solutions.
- JMB17Bronze Contributor
It's against the ground rules, but since I don't have LET I did use named formulas. Including the length of those it is 313 characters (264 if I had only used 1 character for each name, neither accounts for the characters LET would have required).
Although the challenge specifically called for getting amounts from the last two purchases, I modified it so the next N units for which to get the cost is variable (even though you have 181 in inventory, what is the cost of the next 5, for example). So, instead of pulling from the bottom up when N is less than the net inventory (LIFO), it should still pull from the top down (FIFO - and accounting for the units already sold).
- TwifooSilver Contributor
Even without LET, we can still derive the correct results with a formula that conforms to the rules of the challenge. If you have the courage to comply, you can perhaps devise an obedient solution.
- JMB17Bronze Contributor
Sadly, I'm still on office 2016 and have few, if any, functions besides sumproduct that natively accept array arguments. Not using CSE or named formulas makes for a lot fewer tools in the toolbox.
One point of clarification, though. Do you intend for the formula to only analyze the last two entries or dynamic? Say the Sales were 200, leaving 2411 in inventory? It appears the expected results formula(s) are hardwired to only look at the last two purchases (but I know they are overly simplistic and not your actual solution).
- PeterBartholomew1Silver Contributor
It was all going so well! Because the formulae used in the calculation prevented the function being called with the array of 5 products, I set out to use recursion to build the solution array.
This involved a wrapper function FOREACH that builds the recursion stack and XSTACK that extracts results following the return from each level.
I returned the array of inventory values as required. Then I tried to SUM the amounts directly. It turns out that the result is not recognised as an array, despite the fact that it may be output as an array.
Any ideas?
- rpbenzCopper ContributorNewer versions than 1907 Excel, calculation is broken. xlcalculationAutomatic will take over 50times longer in new versions (2012 and 2101) than early versions. Its totally screwed up.
Microsoft messed the terms. If former time there were Office 95,...,2010, 2013,... etc and we called these as versions, now it's Office/Microsoft 365 and versions are actually branches (my understanding) within it. Currently from 1908 on one of semi-annual channels to 2013 on beta. Yes, there are such Excel editions as Excel 2019 and recently announced Excel 2021, not sure about versioning in them.
As for the performance - can't comment, no stats and nothing concrete is here. In general after the year 2018 fall performance is only improving.