In the classic days of Starbucks before these newfangled sticker-printers and wireless drivethrough headset palmtop scanner dealies, in the heady days of 2000-ish, Starbucks had a concise system for describing beverages with a strict syntax and accompanying encoded form.

Vertically on the cup there are a series of labelled boxes which may contain certain values.:

[   ]     -blank-  X  1/2  1/3  2/3
[   ]     -blank-  0 1 2 3 ... 20    followed by    S T G V
[   ]     -blank-  V M C H
[   ]     -blank-  Wh % N B
[    ]    -blank- ((-|x)?(F|WC|M|CR) )+
[   ]     E EM ECP A L C CM

But we will use a horizontal form for easier manipulation. These are the names of the 6 fields of the input which may be any convenient form. Examples here are simple CSV. The Custom field and Syrup fields each may be a concatenation of several values. You may use any convenient subdelimiter. Examples here use space. The Shots field is also a concatenation of a (possible) number and a designation of the cup size (which is implicit in the physical cup but naturally fits in this place in the encoding).

The Decaf field values are translated into words like this

-blank-   -nothing-
X         "decaf"
1/2       "half-caf"
1/3       "one-third-caf"
2/3       "two-thirds-caf"

Logically, the fractional values are only permissible when the number of shots is divisible by the denominator; but for this challenge it is not necessary to detect or diagnose this error.

The Shots field should be broken into its numerical component (if present) and the size identifier. The number should be displayed in latin tuple form: single, double, triple, quadruple, quintuple, beyond 5 just print the number and the suffix "-tuple" ie. 7-tuple, 16-tuple. The size designations are:

S  short
T  tall
G  grande
V  venti


2T  double tall
3G  triple grande

The Syrup field may contain one or more of several optional-number/lettercode tokens. The maximum number of a syrup is 20. Even that is too much. LOL.

V  vanilla
C  caramel
M  mocha
H  hazelnut


2V 2C     two vanilla two caramel
H         hazelnut
M H 1V    mocha hazelnut one vanilla

The Milk field may contain one of the following.

Wh  whole-milk
%   two-percent
N   skim
B   breve

The Custom field may contain one or more of several modifiers with optional x or - sign.

x  extra     F   foam
-  no        WC  whip
             M   mocha drizzle
             CR  caramel drizzle

The Drink field contains one drink id.

E    espresso
EM   espresso macchiato
ECP  espresso con panna
A    americano
L    latte
C    cappuccino
CM   caramel macchiato

Any field may be blank except for the size designation and the drink id.


,3G,V,Wh,,L    =>   triple grande vanilla whole-milk latte
X,2T,,N,,L     =>   decaf double tall skim latte
1/2,V,,,,CM    =>   half-caf venti caramel macchiato
2/3,3V,3V,B,WC,L  => two-thirds-caf triple venti three vanilla breve whip latte
,G,,,xCR,CM    =>   grande extra caramel drizzle caramel macchiato
X,4T,2M 2C,B,xWC -F xM,C  =>
  decaf quadruple tall two mocha two caramel breve extra whip no foam extra mocha drizzle cappuccino

There is no need to diagnose erroneous or illogical concoctions such as a "no foam ... cappuccino*.

Additional Complication

For only the drinks with the word espresso, the number single should be replaced by solo and double should be replaced by doppio. And the size may be unspecified and omitted. Also, just for the bare espresso drink itself, if the only other specification is one of these two specially-replaced numbers-of-shots, then the word espresso itself should be omitted.

,2,1V,B,,EM   =>  doppio one vanilla breve espresso macchiato
,1,,,,E       =>  solo
,2,,,,E       =>  doppio
,3,,,,E       =>  triple espresso

Additional observations on 'reading a cup'

This part is not a requirement for this challenge but may be a useful refinement in applying this sort of verbalized system in other domains.

I mentioned syntax at the very beginning, and by applying the schema to incoming streams of words, orders could be transmitted verbally by several order takers to the barista making beverages. The rough framework SIZE .... DRINK provides demarcation between several orders spoken at once. There is thus a tendency to make all the interior attributes as adjectival phrases so the order also has a valid English noun-phrase structure. So whip is often spoken with whip or whipped and foam is often spoken foamy or with foam.

Thus, for some old-time baristas, they're not "correcting you" when they repeat the order in the proper format (well,... sometimes). They're simply organizing the information for the purpose of remembering all the details exactly. By schematizing the information, an entire beverage order becomes a single unit for the purpose of applying the magic number 7 plus or minus 2. So a barista with this skill can hold 5-9 orders in their head provided other distractions do not consume too much head room. :)


Python, 824 815 807 805 bytes

f=lambda s,a=0:a==0and" ".join(" ".join(f(" "+s.split(",")[i],(["X,deC,1/2,half-C,1/3,oneT-C,2/3,twoTs-C,C,caf,T,-third","".join(" %i"%i+f(",%i-tupL,"%i,["1","x","2","y"])for i in range(6,21))+"1,solo,2,doppio,"*('E'in s)+"1,singL,2,doubL, 3,tripL, 4,quadrupL, 5,quintupL,x,1,y,2,S,short,T,tall,G,grande,V,Vnti","V, vanilla,H, hazelnut,10,ten,11,eleVn,12,twelV,13,thirT,14,4T,15,fifT,16,6T,17,7T,18,8een,19,9T,20,twenty,1,one,2,two,3,three,4,four,5,fiV,6,six,7,seVn,8,eight,9,nine,T,teen","Wh,whole-milk,%,two-percent,N,skim,B,breV","x,extra ,-,no ,F,foam,WC,whip,M,MR,R, drizzL","CM,xM,E,espresso,M, macchiato,CP, con panna,A,americano,L,latte,C,cappuccino,x,C"][i]+",C, caramel,M, mocha,L,le ,V,ve").split(","))for i in range(6-(s in",1,,,,E,2,,,,E"))).split())or a and f(s.replace(a[0],a[1]),a[2:])or s

Slightly more readable:

def f(s,a=0):
 if a==0:
  a+=["".join(" %i"%i+f(",%i-tupL,"%i,["1","x","2","y"])for i in range(6,21))+"1,solo,2,doppio,"*('E'in s)+"1,singL,2,doubL, 3,tripL, 4,quadrupL, 5,quintupL,x,1,y,2,S,short,T,tall,G,grande,V,Vnti"]
  a+=["V, vanilla,H, hazelnut,10,ten,11,eleVn,12,twelV,13,thirT,14,4T,15,fifT,16,6T,17,7T,18,8een,19,9T,20,twenty,1,one,2,two,3,three,4,four,5,fiV,6,six,7,seVn,8,eight,9,nine,T,teen"]
  a+=["x,extra ,-,no ,F,foam,WC,whip,M,MR,R, drizzL"]
  a+=["CM,xM,E,espresso,M, macchiato,CP, con panna,A,americano,L,latte,C,cappuccino,x,C"]
  return" ".join(" ".join(f(" "+s.split(",")[i],(a[i]+",C, caramel,M, mocha,L,le ,V,ve").split(","))for i in range(6-(s in",1,,,,E,2,,,,E"))).split())
 elif a:
  return f(s.replace(a[0],a[1]),a[2:])
  return s

Demo at https://repl.it/C8Hz/3


Javascript ES6, 902 900 bytes

s=>(R=(l,r)=>(l.split`.`.map((e,i,l)=>!(i%2)&&(r=r.split(e).join(l[i+1]+" "))),r),R("-.no.!.-tuple.1/2.half-caf.1/3.one-third-caf.2/3.two-thirds-caf.ECP.Econ panna.CM.RY.CR.RZ.EM.EY.WC.whip.Wh.whole-milk.A.americano.B.breve.C.cappuccino.D.extra.E.espresso.F.foam.G.grande.H.hazelnut.I.venti.L.latte.M.OZ.N.skim.O.mocha.R.caramel.S.short.T.tall.V.vanilla.X.decaf.Y.macchiato.Z.drizzle.%.two-percent",R(",1,,,,E.solo.,2,,,,E.doppio",s).split`,`.map((e,i)=>(`1VI.2MO.2CR.4xD`.split`.`.map(k=>k[0]==i&&(e=e.split(k[1]).join(k[2]))),i==1?(e=R("1!.single.2!.double.3!.triple.4!.quadruple.5!.quintuple",e.replace(/(\d+)/,"$1!"))):i==2?(e=R("10.ten.20.twenty.11.eleven.12.twelve.13.thir0.14.four0.15.fif0.16.six0.17.seven0.18.eigh0.19.nine0.0.teen.1.one.2.two.3.three.4.four.5.five.6.six.7.seven.8.eight.9.nine",e)):s.includes`E`&&(e=R("single.solo.double.doppio",e)),e)).join` `).replace(/\s+/g," ").trim())


  R=(l,r)=>(l.split`.`.map((e,i,l)=>!(i%2)&&(r=r.split(e).join(l[i+1]+" "))),r),                                   // consecutive string replacement function
  R("-.no.!.-tuple.1/2.half-caf.1/3.one-third-caf.2/3.two-thirds-caf.ECP.Econ panna.CM.RY.CR.RZ.EM.EY.WC.whip.     // replace all symbols with appropriate values
    R(",1,,,,E.solo.,2,,,,E.doppio",s)                                                                             // if special espresso cases, directly replace entire string
    .split`,`.map((e,i)=>(                                                                                         // split input at commas
      `1VI.2MO.2CR.4xD`.split`.`.map(k=>k[0]==i&&(e=e.split(k[1]).join(k[2]))),                                    // substitute duplicate symbols with unique symbols
      i==1?(e=R("1!.single.2!.double.3!.triple.4!.quadruple.5!.quintuple",e.replace(/(\d+)/,"$1!"))):              // if in shots section, expand all numbers
      i==2?(e=R("10.ten.20.twenty.11.eleven.12.twelve.13.thir0.14.four0.15.fif0.16.six0.17.seven0.18.eigh0.        // if in syrup section, expand all numbers
      s.includes`E`&&(e=R("single.solo.double.doppio",e)),                                                         // replace single,double with solo,doppio if espresso is in the string
    e)).join` `).replace(/\s+/g," ").trim())                                                                       // join sections, cleanup whitespaces

Example runs:

f(",3G,V,Wh,,L")              -> triple grande vanilla whole-milk latte
f("X,2T,,N,,L")               -> decaf double tall skim latte
f("1/2,V,,,,CM")              -> half-caf venti caramel macchiato
f("2/3,3V,3V,B,WC,L")         -> two-thirds-caf triple venti three vanilla breve whip latte
f(",G,,,xCR,CM")              -> grande extra caramel drizzle caramel macchiato
f("X,4T,2M 2C,B,xWC -F xM,C") -> decaf quadruple tall two mocha two caramel breve extra whip no foam extra mocha drizzle cappuccino
f(",2,1V,B,,EM")              -> doppio one vanilla breve espresso macchiato
f(",1,,,,E")                  -> solo
f(",2,,,,E")                  -> doppio
f(",3,,,,E")                  -> triple espresso
  • \$\begingroup\$ Try this: (R=...)("=-.no.!... (define R inline then use the result as a function) \$\endgroup\$ – Conor O'Brien Jul 3 '16 at 15:55

Ruby, 957 bytes

Perhaps I used the wrong approach; I might rushed out a solution especially after I found out that, in the middle of working out my answer, the question had been accepted. However, the way that it is now it's even worse than Javascript...

In fact, most likely instead of giving suggestions to golf my answer further, another Ruby coder should just submit a much better answer

Try it online while I cry in the corner

->s{q=->n,c{n.find{|x|x=~/^ ?#{c}/i}||''};e='espresso'
([->c{c>''?(c==?X?'de':c['/2']?'half-':c[?1]?'one-third-':'two-thirds-')+'caf':c},->c{c.sub(/(\d*)(.)?/){i=$1.to_i;(i>0?i<6?%w"0 sing doub trip quadrup quintup"[i]+'le':$1+'-tuple':$1)+($2?' '+q[%w"short tall grande venti",$2]:'')}},->c{c.gsub(/(\d*)(\w)/){i=$1.to_i;n=%w"0 one two three four five six seven eight nine ten eleven twelve thir four fif";n+=n[6..9]<<'twenty';(i>0?n[i]+(i>12&&i<20?'teen ':' '):$1)+q[%w"vanilla caramel mocha hazelnut",$2]}},->c{{?%=>'two-percent','Wh'=>'whole-milk',?N=>'skim',?B=>'breve'}[c]||''},->c{c.gsub(/(x|-)?(\w+)/){({?x=>'extra ',?-=>'no '}[$1]||'')+q[%w"foam whip"+%w"caramel mocha".map{|x|x+' drizzle'},$2[0]]}},->c{c.gsub(/CM|CP|./){{'CP'=>' con pana','CM'=>'caramel'+m=' macchiato'}[$&]||q[%w"americano latte cappuccino"<<m<<e,$&]}}].zip(s.split ?,).map{|a,b|a[b]}-['']).join(' ').sub(/(single|double)(?=.*#{e})/){q[%w"solo doppio",$&[0]]}.sub /o #{e}$/,'o'}
  • \$\begingroup\$ I'll update acceptance if the status changes! \$\endgroup\$ – luser droog Jul 4 '16 at 1:51
  • 1
    \$\begingroup\$ Maybe? I'm just not very good at info compression like the other two answers; maybe someone else will be the one to beat Python since this is a very naive solution \$\endgroup\$ – Value Ink Jul 4 '16 at 2:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.