13
\$\begingroup\$

There have been many questions involving calculators; however, it does not appear that any involve implementing a graphing calculator.

The Challenge

You are to write a complete program that takes multiple formulas as input from STDIN and graphs them to STDOUT. Input will take the form f1(x)=x^2-x-1. There will be an f followed by a number 0-9 (inclusive), followed by (x)=, followed by the formula to graph. Your program should be able to take input, graph, take more input, graph, etc.

This is code golf.

Your graph should have the X-axis range from -5 to 5, with a resolution of at least one point every 1/2 unit. Y-axis requirements are the same. This may seem like a small range compared to modern calculators, but it will most likely be trivial in increase this. The graph should have the axis drawn on them, with tick marks in the form of + on the integers.

The formula should be evaluated with the normal order of operation. There will not be any vertical asymptotes/undefined regions in these formulas. The variable will always be x. If two formulas are entered with the same equation number, the oldest one should be erased and replaced with the new formula. Blank formulas should evaluate to zero. Since it is likely that the formula will not always give a nice multiple of 1/2, you are to round to the nearest 1/2.

When a formula is graphed, its line should be formed out of the number of the formula. When a line crosses an axis, the axis should be drawn on top. When two lines cross each other, it does not matter which is shown.

Example Input

f1(x)=x+1

Output

          +       1
          |      1
          +     1
          |    1
          +   1
          |  1
          + 1
          |1
          +
         1|
+-+-+-+-+-+-+-+-+-+-+
       1  |
      1   +
     1    |
    1     +
   1      |
  1       +
 1        |
1         +
          |
          +

Input

f2(x)=(x^2)^0.25

Output

          +       1
          |      1
          +     1
          |    1
          +   1
          |  1
2222      + 1    2222
    222   |1  222
       22 + 22
         2|2
+-+-+-+-+-+-+-+-+-+-+
       1  |
      1   +
     1    |
    1     +
   1      |
  1       +
 1        |
1         +
          |
          +

Input

f1(x)=-x  

(note, it is acceptable for your program to reject this input and only except 0-x or x*-1, but this should be documented)

Output

1         +
 1        |
  1       +
   1      |
    1     +
     1    |
2222  1   +      2222
    2221  |   222
       22 + 22
         2|2
+-+-+-+-+-+-+-+-+-+-+
          |1
          + 1
          |  1
          +   1
          |    1
          +     1
          |      1
          +       1
          |        1
          +         1
\$\endgroup\$

3 Answers 3

6
\$\begingroup\$

Ruby, 200 characters

f={}
r=0..20
(f[gets[1]]=$_[6..-1].gsub /\^/,'**'
s=r.map{' '*21}
f.map{|n,k|r.map{|y|x=y*0.5-5
v=(2*eval(k)).round
v.abs<11&&y!=10&&s[10-v][y]=n
s[y][10]='+|'[y%2]
s[10][y]='+-'[y%2]}}
puts s)while 1

A plain ruby implementation using the standard evaluator for expressions (^ will be replaced so that the examples given above work fine). It is not very robust and assumes the input exactly as specified in the question.

\$\endgroup\$
5
  • \$\begingroup\$ On the fifth line, could you change y*0.5 to y/2 and get rid of two characters? I don't know Ruby, so I may not be right. \$\endgroup\$
    – PhiNotPi
    Nov 26, 2011 at 19:02
  • 2
    \$\begingroup\$ @PhiNotPi Unfortunately this won't work. y/2 does integer division. \$\endgroup\$
    – Howard
    Nov 26, 2011 at 19:14
  • \$\begingroup\$ Can you use loop{} instead of ()while 1? \$\endgroup\$
    – defhlt
    Jul 16, 2012 at 17:14
  • \$\begingroup\$ Found this via the link on the side bar to the right hand side. This is pretty well done. I had some fun trying to get this smaller, but I only found 9 bytes, one byte relying upon the rational literals introduced in ruby 2.1 (?). \$\endgroup\$
    – blutorange
    May 13, 2015 at 19:09
  • \$\begingroup\$ Shaved another 4 bytes to 186. pastebin.com/3E1PjsXB \$\endgroup\$ Jun 12 at 12:06
5
\$\begingroup\$

Perl, 177 characters (+1 command line switch)

perl -nE 's!\^!**!g;s!x!(\$k/2-6)!g;s/\d.*=/;/;$f[$&]=$_;my%a;for$k(@x=2..22){$i=0;$a{int 12.5-2*eval}[$k-2]=$i++for@f}$p="|";$$_[10]=$p^=W,$a{12}=[$p."-+"x10],say map$_//$",@$_ for@a{@x}'

Per this meta thread, I believe this should count as 178 characters in total.

Like the Ruby solution, I'm also using eval and replacing ^ with **.

The input parsing is both incredibly fragile and incredibly robust at the same time: f1(x)= can be written as f 1 ( x ) = or foo 1 bar = or even just 1=, but very strange things could happen if you replaced the f with something that isn't a valid, side-effectless Perl statement. You have been warned.

Other details of interest include the way the vertical axis is drawn, which exploits the fact that the bitwise XOR of the characters + and | is W. Obviously, this won't work on EBCDIC systems.

The output is rendered into a hash of arrays, not an array of arrays — it turns out that it takes fewer chars to explicitly truncate the hash keys to integers and then loop over a hash slice than it takes to ensure that an array isn't indexed with negative values. I could shave off two more characters if it weren't for the annoying way Perl's int truncates negative values towards zero, which forced me to number the output rows from 2 to 22 instead of 0 to 20 in order to avoid rounding artifacts at the top edge of the output area.

I do make use of Perl's liberal string-to-number conversion in the input parsing, where I use the entire string 1(x)= as an array index (it gets converted to just 1).

I could also save three more characters (and make the parsing slightly more robust) by replacing s/\d.*=/;/;$f[$&]=$_ with /\d.*=/;$f[$&]=$', but then I'd have to spend the same number of extra characters to write $' as $'\'' in a single-quoted shell string. I suppose technically I wouldn't have to count those, but it feels sort of like cheating.

\$\endgroup\$
5
\$\begingroup\$

Python 2: 320 characters

N=20
r=range(N+1)
d={}
while(1):
 l=raw_input()
 d[l[1]]=l[6:].replace('^','**')
 g=[[' ']*(N+1) for i in r]
 for n,f in d.items():
  for x in r:
   v=N/2+int(round(2*eval(f.replace('x','(%f)'%(x/2.0-N/4)))))
   if 0<=v<=N:g[N-v][x]=n
 for i in r:
  g[i][N/2]='+|'[i%2]
  g[N/2][i]='+-'[i%2]
 for l in g:print''.join(l)

Could probably be made shorter, but I'm a bit of a newbie at this :)

Making N a variable wastes 9 chars but I like it better that way.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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