# There's no such thing as a free lunch

...or is there?

Your challenge is to parse my lunch bill, which contains the base price, tips, discounts, coupons, and extras and find out if my lunch was $0 or less. If this is the input: 12.34 15 tip 25 discount 1.5 extra 2 coupon  Then the output might be false. Here's how it works: 12.34 is the base price. 15 tip means to add 15% to the total. 25 discount means to subtract 25% from the total. 1.5 extra means to add 1.5 to the total. 2 coupon means to subtract 2 from the total. There may be any amount of tips, discounts, coupons, and extras, but there will always be one base price. Then we do (12.34 * 1.15) * 0.75 + 1.5 - 2 for an output of 10.14. 10.14 is greater than 0, so we output false. My lunch was not free. # Rules number tip means to add number percent to the total. number discount means to subtract number percent from the total number extra means to add number to the total number coupon means to subtract number from the total # Another example: 10 20 tip 20 discount 2 coupon 2 coupon 1 coupon 50 discount 2.55 coupon  The price is -0.24 ((10 * 1.20 * 0.80 - 2 - 2 - 1) * 0.5 - 2.55), so the output is true (my lunch was free.) Notes: • Precision must be at least 2 decimal places. • You can take input as a string with newlines (trailing newline optional) or another separation character, or an array/list of the inputs. • Does the input need to have the name, or can we assume the order if we just input an array of numbers [12.34,15,25,1.5,2]? – Sinusoid Apr 27 '17 at 18:19 • @StewieGriffin You can't chose the order. There may be more than 5 rows or maybe less. You can take 2 coupon as 2.00 coupon, and 15 tip as 0.15 tip – programmer5000 Apr 27 '17 at 18:29 • Is this input case-sensitive? Is that all of the words we need to support? – Rɪᴋᴇʀ Apr 27 '17 at 18:30 • @Riker that is all the words needed, and the input will always be lower case. – programmer5000 Apr 27 '17 at 18:31 • How does the evaluation order work? For example, if we have a discount, then a tip, does the tip apply to the original amount or the discounted amount? – user62131 Apr 27 '17 at 18:42 ## 12 Answers # 05AB1E, 3733 34 bytes I|vy#0èÇ7%">* - (>* +"#sè.V}î0›_  Try it online! Explanation Borrows the mod 7 trick from Jonathan Allan's Jelly answer I # initialize stack with first input |v # loop over all other inputs y# # split input on space as separate to stack 0èÇ # get the character code of the first letter of the type 7% # mod by 7 ">* - (>* +"# # push the list ['>*','-','(>*','+'] where # '>*' = increment and multiply # '-' = subtract # '(>*' = negate, increment, multiply # '+' = add s # swap the top 2 items on the stack è # use the mod result to index into the list .V # run as 05AB1E code } # end loop î0›_ # check if the result rounded up to nearest integer # is less than or equal to 0  • I get 1 when the value is <1. – 12431234123412341234123 Apr 28 '17 at 15:56 • @12431234123412341234123: Good catch. The comparison apparently cast to integer :/ – Emigna Apr 28 '17 at 16:35 ## JavaScript (ES6), 88 85 bytes Takes input as an array of strings. Returns 0 for not free or 1 for free. a=>a.map(s=>([a,b]=s.split ,t+={e:+a,c:-a,t:x=t*a/100,d:-x}[(b||'e')[0]]),t=0)|t<=0  ### How it works Each line is split on the space to get a = amount, b = type of operation. If there's no operation at all (which is the case on the first line), b is set by default to "e" for "extra". To add the correct amount to the total t, we use an object whose keys are the first letter of the operation: { e: +a, // extra c: -a, // coupon t: t * a / 100, // tip d: -t * a / 100 // discount }  Note: If the bill consisted of only one element, map() would return a single-element array which would be coerced to an integer when applied the | operator, making the final test fail. But the OP confirmed that this can't happen. (Arrays of 2 or more elements are coerced to 0.) ### Demo let f = a=>a.map(s=>([a,b]=s.split ,t+={e:+a,c:-a,t:x=t*a/100,d:-x}[(b||'e')[0]]),t=0)|t<=0 console.log(f([ '12.34', '15 tip', '25 discount', '1.5 extra', '2 coupon' ])) console.log(f([ '10', '20 tip', '20 discount', '2 coupon', '2 coupon', '1 coupon', '50 discount', '2.55 coupon' ])) # CJam, 45 42 bytes q~Sf/(sd\{L(d\~ci6%"1\-* + )* -"S/=~}fL0>!  Take input as an array of strings, and takes the tip and discount as decimals. Try it online! Explanation q~ e# Read and eval the input. Sf/ e# Split each string by spaces. (sd e# Pull out the first element (base price) and cast it to a double. \ e# Bring the array back to the top. { e# For each element L in the array: L e# Push L. (d e# Pop out the first element and cast it to a double. \~ e# Bring the second element to the top of the stack. ci6% e# Mod its first character's ASCII value by 6. (c,d,e,t) -> (3,4,5,2) "1\-* + )* -"S/ e# Push this string and split it on spaces. = e# Get the element given by number from the mod. CJam uses modular arrays, e# so 4 and 5 get elements 0 and 1 respectively. ~ e# Eval whichever string was retrieved. }fL e# (end of loop) 0>! e# Check if it's not greater than 0.  The code which is evaluated depending on the first letters: t -> ")*" Adds 1 to the tip amount and multiplies it by the current price. d -> "1\-*" Subtracts the discount amount from 1 and multiplies it by the current price. e -> "+" Adds the extra amount to the current price. c -> "-" Subtracts the coupon amount from the current price.  # Jelly, 42 39 bytes ⁾_@ ⁾C× ”+ ⁾‘× ḲµṪḢO%7µĿṭ ḢW;Ç€j”µFV>0¬  Takes a list of strings with decimal formatted numbers (Leading zeros will work, but have the side effect of printing zeros to STDOUT prior to the final result). Try it online! - not free; or free. ### How? ⁾_@ - Link 1: a coupon ⁾_@ - literal "_@" - the Jelly code for subtraction with reversed arguments ⁾C× - Link 2: a discount ⁾C× - literal "C×" - the Jelly code for complement (1-input) then multiply ”+ - Link 3: extra cost ”+ - literal '+' - the Jelly code for add ⁾‘× - Link 4: a tip ⁾‘× - literal "‘×" - the Jelly code for increment (input+1) then multiply ḲµṪḢO%7µĿṭ - Link 5, switch: char list Ḳ - split on spaces (gives [amount, type] as char lists) µ µ - monadic chain separation to get a value, say v Ṫ - tail (get the type: "coupon", "discount", "extra", or "tip") Ḣ - head (get the first character: 'c', 'd', 'e' or 't') O - cast to ordinal (99, 100, 101, or 116) %7 - mod 7 (1, 2, 3, or 4) Ŀ - call link v as a monad ṭ - tack to the amount char list ḢW;Ç€j”µFV>0¬ - Main link: list of strings (char lists) Ḣ - head - the base price char list W - wrap in a list Ç€ - call the last link (5) as a monad for €ach of the rest ; - concatenate ”µ - literal 'µ' - Jelly's monadic chain separator j - join all the parts with 'µ's "10",".2 tip",".2 discount", "2 coupon","2 coupon","1 coupon",".5 discount","2.55 coupon": F - flatten (makes a char list, for example: "10µ.20‘×µ.20C×µ2_@µ2_@µ1_@µ.50C×µ2.55_@") V - evaluate as Jelly code (the above evaluates to -0.2499999999999991) >0 - greater than 0? ¬ - not  • Consistently outputs 0 for me... – programmer5000 Apr 27 '17 at 19:42 • Ah, maybe I should say the format uses decimal? – Jonathan Allan Apr 27 '17 at 19:47 • Oh. Yes, you should. – programmer5000 Apr 27 '17 at 19:48 • I am writing up the explanation at the mo, here is an example of a free lunch. – Jonathan Allan Apr 27 '17 at 19:48 # GNU sed + dc, 117111 107 bytes Using -z interpreter flag (included in score as 1 byte): s/discount/_tip/g s/tip/.01*1+*/g s/extra/+/g s/coupon/-/g s/.*/dc -e '& 0r-p'/e s/[^-]*$/free/
s/-/not /


## Explanation

#!/bin/sed -fz

# Convert to dc expression (discount is just a negative tip)
s/discount/_tip/g
s/tip/.01*1+*/g
s/extra/+/g
s/coupon/-/g

# Run dc
s/.*/dc -e '& 0r-p'/e

# Convert to pretty output
s/[^-]*$/free/ s/-/not /  Since the input is already very close to Reverse Polish notation, it's a simple matter to transform extra and coupon to + and -, and not much more to change the percentages into multipliers. Then invoke dc and produce a readable result depending on whether - is found (we have to negate the result, so a - implies "not free", otherwise 0 would be a special case that would need its own handling). ## Example The second case from the question is: 10 20 tip 20 discount 2 coupon 2 coupon 1 coupon 50 discount 2.55 coupon  That becomes this dc program: 10 20 .01*1+* 20 _.01*1+* 2 - 2 - 1 - 50 _.01*1+* 2.55 - 0r-p  Resulting in: free  # JavaScript, 173169 145 bytes i=>{w=i.split\n.map($=>$.split );t=+w.shift()[0];p=$=>t*$/100;w.map(x=>{k=+x[0];f=x[1][0];l={e:k,c:-k,t:p(k),d:-p(k)},t+=l[f]});return t<=0;}  There should still be plenty of golfing to do Try it online! (145 bytes currently) Try it out: <script>var _=i=>{w=i.split('\n').map($=>$.split(' '));t=+w.shift()[0];p=$=>t*$/100;w.map(x=>{k=+x[0];f=x[1][0];t+=f=='e'&&k||f=='c'&&(-k)||f=='t'&&p(k)||f=='d'&&(-p(k))});return t<=0;}</script> <textarea oninput="document.querySelector('pre').innerText=_(this.value)"></textarea> <pre></pre> Thanks to programmer5000 for all his golfing advice • Why is node required? – programmer5000 Apr 27 '17 at 19:56 • Also, you can do {w=i.split<nl> where <nl> is a literal newline – programmer5000 Apr 27 '17 at 19:57 • Node is not required. I just used it for testing on TIO – Alberto Rivera Apr 27 '17 at 19:57 • I added a stack snippet for trying it out. Feel free to rollback if you don't like it. – programmer5000 Apr 27 '17 at 20:02 • You can remove the f= part, it is allowed by the rules, and you can replace $.split(' ') with $.split . – programmer5000 Apr 27 '17 at 20:04 # JavaScript (ES6), 97 107 Input as a multiline string with a trailing newline. t=>t.replace(/(\S+) ?(.*)?\n/g,(x,d,b)=>t-=b>'t'?-t*d/100:b>'e'?d:b>'d'?t*d/100:b?-d:d,t=0)&&t>=0  The regexp splits the numeric and optional text part for each line in d and b. The calculations should be more or less obviuos. Just some note: - using -= to avoid problems mixing number with strings - the sum is negated to save 1 byte, so the last check is for >= 0 instead of <= 0 PS still way longer than @Arnauld's. Rats. Test var f= t=>t.replace(/(\S+) ?(.*)?\n/g,(x,d,b)=>t-=b>'t'?-t*d/100:b>'e'?d:b>'d'?t*d/100:b?-d:d,t=0)&&t>=0 a=12.34 15 tip 25 discount 1.5 extra 2 coupon  b=10 20 tip 20 discount 2 coupon 2 coupon 1 coupon 50 discount 2.55 coupon  console.log('Not free: '+a,f(a)) console.log('Free: '+b,f(b)) # C# 324 219 bytes bool a(string[] l){var x=0f;foreach(var s in l){var b=float.Parse(s.Split(' ')[0]);if(s.EndsWith("p"))x*=b;else if(s.EndsWith("t"))x*=1-b;else if(s.EndsWith("n"))x-=b;else if(s.EndsWith("a"))x+=b;else x=b;}return x<=0;}  It's not pretty, and probably not the best way, but here it is. Requires input be passed as a string array, and tips/discounts passed as floats (0.15 tip instead of 15 tip) as this has been clarified as acceptible in the comments of the spec. Explaination: bool a(string[] l){ //Define method with input string array l and bool output var x=0f; //Initialize float x foreach(var s in l){ //Iterate through lines var b=float.Parse(s.Split(' ')[0]); //Parse the number from the line and store it in float b if(s.EndsWith("p")) //If line ends with "p" then line is "tip" x*=b; //Parse number from line to float add 1 and multiply by x else if(s.EndsWith("t")) //If line ends with "t" then line is "discount" x*=1-b; //Parse number from line to float, subtract from 1 and multiply by x else if(s.EndsWith("n")) //If line ends with "n" then line is "coupon" x-=b; //Parse number from line to float and subtract from x else if(s.EndsWith("a")) //If line ends with "a" then line is "extra" x+=b; //Parse number from line to float and add to x else x=b; //Line is base price } //End foreach return x<=0; //Return x less than or equal to 0 } //End method  There's gotta be a better way to do this, but this works at least • If you've been given tips/discounts as floats then you don't want that 100 in the t branch. – Wai Ha Lee Apr 28 '17 at 15:58 • @WaiHaLee oops, good point, forgot to change that to 1 – Skidsdev Apr 28 '17 at 16:01 • Tip: put float.Parse(s.Split(' ')[0]) into something to reduce duplication. That'll save about 80 characters. – Wai Ha Lee Apr 28 '17 at 16:02 • oh wow I'm a terrible golfer I didn't even remove unnecessary whitespace. I blame Visual Studio. – Skidsdev Apr 28 '17 at 16:09 • Not a bad effort at all! – Wai Ha Lee Apr 28 '17 at 16:11 # PowerShell, 218156 143 bytes ($n=$args)|%{[float]$v,$w=$_-split' ';switch -w($w){"t*"{$t+=$v}"d*"{$d+=$v}"e*"{$e+=$v}"c*"{$c+=$v}}};($n[0]*(1+$t/100)*(1-$d/100)+$e-$c)-lt 0


Try it online!

EDIT Saved bytes by splitting the piped variable beforehand

EDIT 2 Stored second part of string so I could make better wildcard calls

• Seems to work, and your input format is fine. – programmer5000 Apr 27 '17 at 19:28

# Python 133 bytes

def f(b):
t=float(b.pop(0))
for l in b:
v,a=l.split(' ');v=float(v);t+={'t':t*v/100,'d':-t*v/100,'c':-v,'e':v}[a[0]]
return t<=0


Similar to the JavaScript ES6 version. But type conversion is required for float values in Python.

Explanation:

Extract the first value and convert it to float.

For each other line in the bill:

1. split and convert the value to float
2. Use a dict to select the right operation according to the first letter
3. Accumulate the value

Usage:

print(f([
'12.34',
'15 tip',
'25 discount',
'1.5 extra',
'2 coupon'
]))

print(f([
'10',
'20 tip',
'20 discount',
'2 coupon',
'2 coupon',
'1 coupon',
'50 discount',
'2.55 coupon'
]))

• Welcome to the site! – James Apr 28 '17 at 21:53

# Java 227 bytes

Been a while and still no Java answer that I can see, so here's my C# answer ported to Java, at the cost of 8 bytes

boolean a(String[] l){Float x=0f;for(String s:l){Float b=Float.parseFloat(s.split(" ")[0]);if(s.endsWith("p"))x*=b;else if(s.endsWith("t"))x*=1-b;else if(s.endsWith("n"))x-=b;else if(s.endsWith("a"))x+=b;else x=b;}return x<=0;}


For an explanation and such, see my C# answer

Like that answer, this answer expects that the tip and discount be passed as floats (0.15 not 15)

• Pretty good...for Java! – programmer5000 May 2 '17 at 10:51
• @programmer5000 to be fair C# is only slightly less verbose than Java, main advantages being things like C#'s support of var generic type, and lambdas (I know Java has them, but C#'s are golfier) – Skidsdev May 2 '17 at 10:55

# Jq 1.5, 129119114 112 bytes

reduce (.[]/" "|.[0]|=tonumber|.[1]|=length)as[$n,$c](0;[$n,0,0,.+.*($n/100),0,.+$n,.-$n,0,.-.*($n/100)][$c])<=0


Expanded

  reduce (
.[]/" "             # split each element into [value,command]
| .[0]|=tonumber      # convert value to number
| .[1]|=length        # convert command to length
) as [$n,$c]
(  0
; [ $n # "" -> set base , 0 , 0 , .+.*($n/100)       # "tip"
, 0
, .+$n # "extra" , .-$n               # "coupon"
, 0
, .-.*($n/100) # "discount" ][$c]                # ... depending on command length
) <=0                   # true if lunch was free


Try it online!