12
\$\begingroup\$

Summary

Code golf is good. Pie is good. When you put the two together, only good stuff can happen.

Specifications

In this challenge you will manage a pie shop. The user will be able to input five different commands: list, count, buy, sell, and exit. Here are the specifications for each:

  • list

    • Print a list of all the pies owned, and how many. Separate with | and pad with a space on either side. |s must be aligned. Pie amount may be negative (that means you owe pie to someone :(). For example:

      | apple     | 500 |
      | blueberry | 2   |
      | cherry    | -30 |
      
  • count [type]

    • Print how many {{type}} pies there are. Print "There is no {{type}} pie!" if there is none. {{type}} will always match the regex \w+ (i.e, it will always be a single word). For example, if I had the amount of pies shown in the above example list, then

      > count apple
      500
      > count peach
      There is no peach pie!
      
  • buy [n] [type]

    • Add {{n}} to the count of {{type}} pie, and print it. Create {{type}} pie if it does not exist. {{n}} will always match the regex [0-9]+ (i.e, it will always be a number). Here's another example (with the same pie inventory as the previous examples):

      > count blueberry
      2
      > buy 8 blueberry
      10
      
  • sell [n] [type]

    • Subtract {{n}} from the count of {{type}} pie, and print it. Create {{type}} pie if it does not exist. Pie can be negative (oh no, that would mean you owe someone pie!).

      > sell 15 blueberry
      -5
      > buy 5 blueberry
      0
      
  • exit

    • Print "The pie store has closed!" and exit the program.

      > exit
      The pie store has closed!
      

Further clarifications

  • If a non-existing function is called (the first word), then print "That's not a valid command."
  • If an existing function is called with invalid arguments (the words after the first word), how your program behaves doesn't matter. "Invalid arguments" includes too many arguments, too little arguments, {{n}} not being a number, etc.
  • Pie is good.
  • Your input must be distinguished from your output. If you are running the program on the command line/terminal/shell/other text-based thing, you must prefix input with "> ​" (a "greater than" sign and a space) or some other shell input prefix thing.
  • Pie is good.
  • If all of these clarifications are not good enough, here's some sample output:

    > list
    > buy 10 apple
    10
    > sell 10 blueberry
    -10
    > list
    | apple     | 10  |
    | blueberry | -10 |
    > count apple
    10
    > count peach
    There is no peach pie!
    > exit
    The pie store has closed!
    
  • If you buy/sell pie and the net count becomes 0, you can either keep it in the list or not, and you can either return 0 or There is no {{type}} pie! when you count it.

  • This is ; shortest code wins.
  • Did I mention that pie is good?
\$\endgroup\$
  • 3
    \$\begingroup\$ So just to clarify... is pie good? \$\endgroup\$ – Igby Largeman Aug 22 '13 at 5:19
  • 4
    \$\begingroup\$ Is it acceptable to keep a pie in the list with a count of zero? Like if you do buy 1 apple and sell 1 apple. And would it then be valid for count apple to return 0 instead of There is no apple pie!? \$\endgroup\$ – Igby Largeman Aug 22 '13 at 11:39
  • \$\begingroup\$ @IgbyLargeman Darn it, I thought I clarified everything! :P Added new test case to additional clarifications \$\endgroup\$ – Doorknob Aug 22 '13 at 14:02
  • \$\begingroup\$ @Doorknob hey! I'm outputting "there is no apple pie" after the last one has been sold. \$\endgroup\$ – John Dvorak Aug 22 '13 at 14:16
  • \$\begingroup\$ @JanDvorak Alright, I suppose either way will work. Updated again \$\endgroup\$ – Doorknob Aug 22 '13 at 14:21
3
\$\begingroup\$

Ruby, 335 330

h=Hash.new 0
loop{$><<"> "
puts case gets when/^list/
h.map{|x|?|+" %%%ds |"%h.flatten.map{|e|e.to_s.size}.max*2%x}when/^count (.*)/
h[$1]!=0?h[$1]:"There is no #{$1} pie!"when/^buy#{m=" (.*)"*2}/
h[$2]+=$1.to_i when/^sell#{m}/
h[$2]-=$1.to_i when/^exit/
puts"The pie store has closed!"
break else"That's not a valid command."end}

Some tricks here:

?|+" %%%ds |"%[*h].flatten.map{|e|e.to_s.size}.max*2%x

Doorknob's idea to use a formatter is taken a step further here, literally. First, the longest string in the hash among all keys and values is formatted using " %%%ds |" to produce a string like " %6s |". Yep, no shrinkwrapping each column separately. There was never the requirement to. One size fits all. Then this string is duplicated and used as a formatting string for the two-element array containing the current row. Finally, the + near the start gets its word and prepends a single leading pipe. Oh, and puts has a nice handling of arrays.

Ruby has interpolation in regex literals. It's a tight save, but it does save a little.

Ruby requires semicolons after the when expression, but not before the keyword. This leads to a weird rendering artifact when the semicolon is replaced with a newline.

And, of course, perlism known as magic globals and automatic matching of regex literals against them.

Also, most statements including case are expressions.

\$\endgroup\$
  • \$\begingroup\$ Very clever tricks! +1 \$\endgroup\$ – Doorknob Aug 22 '13 at 14:05
  • \$\begingroup\$ Hmm, but why Hash.new(0) instead of {}? \$\endgroup\$ – Doorknob Aug 22 '13 at 14:55
  • 1
    \$\begingroup\$ @Doorknob Ruby's hashes can have default values (if you pass an object) or even generators (if you pass a block (key, hash -> value). If you don't pass either, the default value is nil (which doesn't allow addition). The literal uses nil as the default value. \$\endgroup\$ – John Dvorak Aug 22 '13 at 15:58
  • \$\begingroup\$ Could save a few characters with h=Hash.new(0) => h=Hash.new 0, print"> " => $><<'> ', and I think [*h] can just be h. I tried putting together a version without the switch statement since all that boilerplate text really adds up: gist.github.com/chron/6315218. I was trying to make something work with ruby -ap but the requirement for the prompt makes it hard :< \$\endgroup\$ – Paul Prestidge Aug 23 '13 at 3:20
  • \$\begingroup\$ @chron thanks! Can't believe I missed the first one, and I'm not sure why I thought $><< printed a newline. As for the last suggestion... unfortunately, hashes don't have a "flatten" method. \$\endgroup\$ – John Dvorak Aug 23 '13 at 3:27
3
\$\begingroup\$

Ruby, 427 384 characters

alias x puts
p={}
loop{
print'> '
case(u=gets.chop.split)[0]when'exit'
x'The pie store has closed!'
exit
when'list'
p.each{|k,v|printf"| %-#{p.keys.map(&:size).max}s | %-#{p.map{|e,a|a.to_s.size}.max}s |\n",k,v}
when'count'
x p[t=u[1]]||"There is no #{t} pie!"
when/sell|buy/
m=(u[0]<?s?1:-1)*u[1].to_i
if p[t=u[2]]
x p[t]+=m
else
x p[t]=m
end
else x"That's not a valid command."
end}

Thanks to Jan Dvorak of huge improvement from 427 to 384 (!)

\$\endgroup\$
  • \$\begingroup\$ You can use loop{...} instead of while 1do...end. \$\endgroup\$ – John Dvorak Aug 22 '13 at 5:27
  • \$\begingroup\$ You can use split without its argument. By default, it splits by whitespace (or $; if that is set) \$\endgroup\$ – John Dvorak Aug 22 '13 at 5:45
  • \$\begingroup\$ p.keys.group_by(&:size).max[0] -- are you looking for p.keys.map(&:size).max or p.map{|x,_|x.size}.max? Here: [(t=p.values).max.to_s.size,t.min.to_s.size].max are you looking for p.map{|_,x|x.to_s.size}.max? I'm gonna take your idea and abuse the formatter, though :-) \$\endgroup\$ – John Dvorak Aug 22 '13 at 5:52
  • \$\begingroup\$ p[t]=p[t]+m is equivalent to p[t]+=m (except p[t] is evaluated twice instead of once) and longer. Use the latter. \$\endgroup\$ – John Dvorak Aug 22 '13 at 5:58
  • \$\begingroup\$ @JanDvorak Oooh, thanks for all the tips :D I thought Ruby didn't have a += operator; that's why I didn't use it. Maybe that's just for ++. I will edit my post shortly \$\endgroup\$ – Doorknob Aug 22 '13 at 14:02
3
\$\begingroup\$

Python Pie-thon 437

I'm sure there is some slack on the second last line, but the requirement to align the bars for both the pie type and number is a doozy.

p,C,l={},"count",len
while 1:
 a=raw_input("> ").split();c=a.pop(0)
 if"exit"==c:print"The pie store has closed!";break
 if"sell"==c:a[0]=int(a[0])*-1
 if c in[C,"buy","sell"]:
  y=a[-1]
  if c!=C:p[y]=p.get(y,0)+int(a[0])
  print p.get(y,"There is no %s pie!"%y)
 elif"list"==c:
  for i in p:print"| %s | %s |"%(i.ljust(l(max(p.keys(),l))),str(p[i]).rjust(max([l(str(x)) for x in p.values()])))
 else:print"That's not a valid command."

As per Igby Largeman's comment the rules are unclear around what to do if there was a pie of a specific type, but there are 0 now. So I've interpreted it in my favour.

Sample output:

> buy 10 apple
10
> sell 1 blueberry
-1
> buy 1 keylime
1
> sell 3 apple
7
> buy 5 blueberry
4
> list
| keylime   | 1 |
| apple     | 7 |
| blueberry | 4 |
> sell 1 keylime
0
> count keylime
0
\$\endgroup\$
  • \$\begingroup\$ Sorry, but in this one > count potato produces That's not a valid command. instead of There is no potato pie! \$\endgroup\$ – Doorknob Aug 22 '13 at 14:06
  • \$\begingroup\$ @Doorknob Are you running it in IDLE? \$\endgroup\$ – user8777 Aug 22 '13 at 23:12
  • \$\begingroup\$ Yes. I'll try in a file \$\endgroup\$ – Doorknob Aug 22 '13 at 23:15
  • \$\begingroup\$ Actually, count doesn't seem to be working at all. Actually it works sometimes but sometimes it doesn't. It's very odd... \$\endgroup\$ – Doorknob Aug 22 '13 at 23:17
  • 3
    \$\begingroup\$ Hehe, I knew it was a naming clash of some kind :P +1 Oh and also, my file for your program had an amusing name: pie.py :P \$\endgroup\$ – Doorknob Aug 23 '13 at 0:49
3
\$\begingroup\$

C# - 571 568 559

Bringing up the rear as usual with the hopelessly verbose C#.

using C=System.Console;class Z{static void Main(){var P=new 
System.Collections.Generic.Dictionary<string,int>();int i=0,n;a:C.Write
("> ");var I=C.ReadLine().Split(' ');var c=I[0];object s=c=="exit"?
"The pie store has closed!":"That's not a valid command.";if(c==
"count")try{s=P[c=I[1]];}catch{s="There is no "+c+" pie!";}if(c==
"buy"||c=="sell"){n=int.Parse(I[1]);n=c=="sell"?-n:n;try{n+=P[c=
I[2]];}catch{}s=P[c]=n;i=(n=c.Length)>i?n:i;}if(c=="list")foreach(
var p in P.Keys)C.Write("| {0,"+-i+"} | {1,11} |\n",p,P[p]);else C.
WriteLine(s);if(c!="exit")goto a;}}

enter image description here

I took some liberty with the rule about list output. To save some characters I hardcoded the width of the count column to the maximum width of an integer value. (The rules didn't say extra spaces weren't allowed.)

Formatted:

using C = System.Console;
class Z
{
    static void Main()
    {
        var P = new System.Collections.Generic.Dictionary<string, int>();
        int i = 0, n;
    a:
        C.Write("> ");
        var I = C.ReadLine().Split(' ');
        var c = I[0];
        object s = c == "exit" ? "The pie store has closed!" 
                               : "That's not a valid command.";

        // allow Dictionary to throw exceptions; cheaper than using ContainsKey()
        if (c == "count")
            try { s = P[c = I[1]]; }
            catch { s = "There is no " + c + " pie!"; }

        if (c == "buy" || c == "sell")
        {
            n = int.Parse(I[1]);
            n = c == "sell" ? -n : n;

            try { n += P[c = I[2]]; }
            catch { }

            s = P[c] = n;
            i = (n = c.Length) > i ? n : i;
        }

        if (c == "list")
            foreach (var p in P.Keys) 
                C.Write("| {0," + -i + "} | {1,11} |\n", p, P[p]);
        else
            C.WriteLine(s);

        if (c != "exit") goto a; // goto is cheaper than a loop
    }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ +1, I'm surprised that you can get such a low character count with such a verbose language :D \$\endgroup\$ – Doorknob Aug 23 '13 at 14:14
  • \$\begingroup\$ My goal with the Java is only to beat the C# implementation. Haha. Nice job with this one. \$\endgroup\$ – asteri Aug 26 '13 at 1:31
2
\$\begingroup\$

Python 3, 310

p={}
c=G=p.get
while c:
 l=("exit list buy count sell "+input("> ")).split();c=l.index(l[5]);*_,n=l
 if~-c%2*c:p[n]=(3-c)*int(l[6])+G(n,0)
 print(["The pie store has closed!","\n".join("| %*s | %9s |"%(max(map(len,p)),k,p[k])for k in p),G(n),G(n,"There is no %s pie!"%n),G(n),"That's not a valid command."][c])
\$\endgroup\$
1
\$\begingroup\$

Java - 772 751 739 713 666 619

I know it's not winning the contest, but just for fun!

import java.util.*;class a{static<T>void p(T p){System.out.print(p);}public static
 void main(String[]s){z:for(Map<String,Long>m=new HashMap();;){p("> ");s=new
 Scanner(System.in).nextLine().split(" ");switch(s[0]){case"list":for(Map.Entry 
e:m.entrySet())System.out.printf("|%12s|%6s|\n",e.getKey(),e.getValue());break;
case"count":p(m.get(s[1])!=null?m.get(s[1]):"There is no "+s[1]+" pie!\n");break;
case"buy":case"sell":long r=(s[0].length()==3?1:-1)*new Long(s[1])+(m.get(s[2])!=null?
m.get(s[2]):0);p(r+"\n");m.put(s[2],r);break;case"exit":p("The pie store has
 closed!");break z;default:p("That's not a valid command.\n");}}}}

With line breaks and tabs:

import java.util.*;

class a{

    static<T>void p(T p){
        System.out.print(p);
    }

    public static void main(String[]s){
        z:for(Map<String,Long>m=new HashMap();;){
            p("\n> ");
            s=new Scanner(System.in).nextLine().split(" ");
            switch(s[0]){
            case"list":
                for(Map.Entry e:m.entrySet())
                    System.out.printf("|%12s|%6s|\n",e.getKey(),e.getValue());
                break;
            case"count":
                p(m.get(s[1])!=null?m.get(s[1]):"There is no "+s[1]+" pie!");
                break;
            case"buy":
            case"sell":
                long r=(s[0].length()==3?1:-1)*new Long(s[1])+(m.get(s[2])!=null?m.get(s[2]):0);
                p(r);
                m.put(s[2],r);
                break;
            case"exit":
                p("The pie store has closed!");
                break z;
            default:
                p("That's not a valid command.");
            }
        }
    }

}
\$\endgroup\$
  • 1
    \$\begingroup\$ +1 for a non-traditional golfing language :). With C# I found that the switch statement is more expensive than simple if{} constructs. This should be true for Java too. \$\endgroup\$ – Igby Largeman Aug 26 '13 at 2:28
  • \$\begingroup\$ @IgbyLargeman Yeah, I kept trying to get if/else to be less expensive, but due to the fact that I'd have to do s[0]=s[0].intern() in order to compare with ==, it always ends up being more. I know, very counterintuitive. \$\endgroup\$ – asteri Aug 26 '13 at 2:29

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.