March 21st, 2004, 08:34 PM

Mixed numbers and decimals?!?
Okay. I have ran into the first problem in python that I havent been able to solve myself, and I am asking for your help. My problem is that I have created a very simple app that calculates a door cutlist for a cabinet shop. The user inputs the different dimensions of the door and the output tells them what to cut. Now the problem seem to be having is the output doesnt give the correct decimals. If you set the values by hand an not allow for user input it works fine. But using input() seems to throw it out the window.
Also some of the dimensions need to be mixed numbers like 2 and 1/2 inches or 2 1/2 for the user input. But when I try this it gives me and invalid syntax error.
My code is as follows:
Code:
from __future__ import division
q = input(" Number of doors: ")
w = input(" Width: ")
l = input(" Length: ")
c = input(" Cut depth: ")
m = input("Machine clearance: ")
s = input(" Stile width: ")
r = input(" Rail width: ")
sl = l
rl = w(2*s)+(2*c)
pl = sl(2*r)(2*c)(2*m)
pw = w (2*s)(2*c)(2*m)
rq = 2*q
sq = 2*q
pq = q
print "Cutlist for: ", q, "doors", w, "x", l
print ""
print " Panels: ", pq, pw, "x", pl
print " Stiles: ", sq, s, "x", sl
print " Rails: ", rq, r, "x", rl
Some test inputs for reproduction are:
Number of doors: 1
Width: 12
Length: 12
Cut Depth: 1/8
Machine clearance: 1/32
Stile width: 2.5
Rail width: 2.5
That will give you the output with incorrect decimals.
For the Mixed number error change the 2.5 to 2 1/2
Any suggestions would be greatly appreciated.
Last edited by DFarist; March 21st, 2004 at 10:51 PM.
March 22nd, 2004, 02:37 AM

the problem is that by default the division operator between two integers returns an integer value. This is the same way that languages like C do it, so it has some historical precedent. To get a floating point number, one of the numbers needs to be a float. e.g.
Code:
>>> 1/2
0
>>> 1.0/2
0.5
>>> float(1)/2
0.5
>>>
However the developers of Python realise this is a problem, and are in the process of changing it. They can't do this all in one go since it will break existing code, so from Python 2.2 this behaviour is deprecated. From version 2.4 the division operator on integers will return a floating point number, which is what you want. If you are using 2.2 or 2.3 then you can get this behaviour now by doing "from __future__ import division" at the start of your code:
Code:
>>> from __future__ import division
>>> 1/2
0.5
See PEP 238 at http://www.python.org/peps/pep0238.html for the full gory details.
This will fix your problem, but will mean your program will fail if run on versions earlier than 2.2. This may or may not be a problem for you, depending on how you distribute it.
This will also still not allow the user to enter values such as 2 1/2, since that is not a valid Python expression. It would need to be written as 2+1/2 or 2.5.
Also, if you are going to run this on a server then using 'input' is not safe  the user could enter any Python statements, and take control of your system, delete files etc.
The alternative is to write your own expression parser, which is a nontrivial task for a novice programmer but would give complete control over how the user's input is interpreted. If you decide to go this route, let us know and I will post some pointers to writing it.
Regards,
Dave  The Developers' Coach
March 22nd, 2004, 03:40 AM

The problem is a bit more interesting  it seems that (on Python 2.3 at least) the input () function ignores the
from __future__ import division command statement.
Code:
>>> from __future__ import division
>>> 1/2
0.5
>>> input()
1/2
0
>>> a = raw_input()
1/2
>>> a
'1/2'
>>> eval(a.strip())
0.5
However, as you can see above combining raw_input() with eval() seems to avoid the problem.
Grim
March 22nd, 2004, 03:44 AM

My guess  this is an oversight (bug) due the the fact that no one really uses input in code that others will use  because of the security issues.
March 22nd, 2004, 08:59 AM

I don't get enough practice with regular expressions but here's my solution:
It allows input in the form integer, floating point and integer with fraction. The fraction is deliberately constrained to only allow single digit numerators.
Code:
from __future__ import division
import re
imperial = re.compile(r"(\d*\s+\d/\d+)(\d/\d+)")
metric = re.compile(r"(\d*\.\d+)(\d+)(?!.*/)")
def NumericInput(prompt="Input value:"):
while True:
inp = raw_input(prompt).strip()
match = imperial.match(inp)
try:
if match:
val = match.group().replace(" ", "+", 1)
value = eval(val)
break
else:
match = metric.match(inp)
if match:
val = match.group()
value = eval(val)
break
except:
pass
print "Input format '1' , '1.5' or '1 1/2', retry..."
return value
q = NumericInput(" Number of doors: ")
w = NumericInput(" Width: ")
l = NumericInput(" Length: ")
c = NumericInput(" Cut depth: ")
m = NumericInput("Machine clearance: ")
s = NumericInput(" Stile width: ")
r = NumericInput(" Rail width: ")
sl = l
rl = w(2*s)+(2*c)
pl = sl(2*r)(2*c)(2*m)
pw = w(2*s)(2*c)(2*m)
rq = 2*q
sq = 2*q
pq = q
print "Cutlist for: ", q, "doors", w, "x", l
print ""
print " Panels: ", pq, pw, "x", pl
print " Stiles: ", sq, s, "x", sl
print " Rails: ", rq, r, "x", rl
I'm sure it can be improved on
Grim
March 22nd, 2004, 04:09 PM

Grim Archon. That method worked exactly like I wanted it to. Thank you so much for your help. I have one more question though. Is there any way to get the output to also be in fractions and mixed numbers or am I just asking to much in that line? If you get tired of typing all this just let me know where to find out information, because I have been looking forever. Thanks again.
March 22nd, 2004, 05:06 PM

My function to handle numberic input, enjoy!
Code:
#!/usr/bin/env python
import re
def nuput(prompt = 'Enter a number: '):
#Parses raw input depending on user input; this can be an int, float or
#Fraction. If an error occurs while parsing then raise a 'TypeError' for
#error catching purposes.
numbers = []
try:
for number in raw_input(prompt).split():
#Loops over each int, float and fraction in the raw_input()
#call and checks that the input fits given patterns.
if len(number) > 1:
if re.search('^(\d+/\d+)$', number):
#Checks if the the string 'number' is a fraction.
#Unpacks numbers and assigns the values to 'one'
#and 'two'.
one, two = map(float, number.split('/'))
#Appends a string of float / float to 'numbers'.
numbers.append('%f/%f' % (one, two))
elif re.search('^(\d*\.\d+)$', number):
numbers.append(number)
elif number.isdigit(): numbers.append(number)
#Returns an evaluted expression of the values passed to nuput().
return eval('+'.join(numbers))
except:
raise TypeError, 'Input must be Decimal, Float or Fraction.'
Spot an error, let me know.
Mark.
March 22nd, 2004, 06:04 PM

It was an fun twist on the temperature conversion problem
This version of the code attempts to match half, 4ths,5ths,8ths,10ths,16ths and 32ths. If it can't then it just prints floating point.
Code:
from __future__ import division
import re
imperial = re.compile(r"(\d*\s+\d/\d+)(\d/\d+)")
metric = re.compile(r"(\d*\.\d+)(\d+)(?!.*/)")
#Generate Fractions
fractions = {}
for n in range(1, 32):
fractions["%0.5f"%(n/32.0)] = "%s/32"%n
for n in range(1, 16):
fractions["%0.5f"%(n/16.0)] = "%s/16"%n
for n in range(1, 10):
fractions["%0.5f"%(n/10.0)] = "%s/10"%n
for n in range(1, 8):
fractions["%0.5f"%(n/8.0)] = "%s/8"%n
for n in range(1, 5):
fractions["%0.5f"%(n/5.0)] = "%s/5"%n
for n in range(1, 4):
fractions["%0.5f"%(n/4.0)] = "%s/4"%n
for n in range(1, 3):
fractions["%0.5f"%(n/3.0)] = "%s/3"%n
fractions['0.00000'] = ''
fractions["0.50000"] = "1/2"
#End of Fractions
def NumericInput(prompt="Input value:"):
while True:
inp = raw_input(prompt).strip()
match = imperial.match(inp)
try:
if match:
val = match.group().replace(" ", "+", 1)
value = eval(val)
break
else:
match = metric.match(inp)
if match:
val = match.group()
value = eval(val)
break
except:
pass
print "Input format '1' , '1.5' or '1 1/2', retry..."
return value
def ImpPrint(value):
neg = ''
if value<0:
neg = ''
value = value
integer = int(value)
remainder = "%0.5f"%(valueinteger)
if fractions.has_key(remainder):
return "%s%d %s"%(neg, integer, fractions[remainder])
else:
return "%s%d.%s"%(neg, integer, remainder)
q = NumericInput(" Number of doors: ")
w = NumericInput(" Width: ")
l = NumericInput(" Length: ")
c = NumericInput(" Cut depth: ")
m = NumericInput("Machine clearance: ")
s = NumericInput(" Stile width: ")
r = NumericInput(" Rail width: ")
sl = l
rl = w(2*s)+(2*c)
pl = sl(2*r)(2*c)(2*m)
pw = w(2*s)(2*c)(2*m)
rq = 2*q
sq = 2*q
pq = q
print "Cutlist for: %4d doors %8s x %8s"%(q, ImpPrint(w), ImpPrint(l))
print ""
print " Panels: %4d %8s x %8s"%(pq, ImpPrint(pw), ImpPrint(pl))
print " Stiles: %4d %8s x %8s"%(sq, ImpPrint(s), ImpPrint(sl))
print " Rails: %4d %8s x %8s"%(rq, ImpPrint(r), ImpPrint(rl))
If you don't like the fractions I chose (do you use 16ths?) then just modify the fraction generator  (the order is important).
I hope this helps
Grim
March 22nd, 2004, 06:10 PM

Originally Posted by netytan
My function to handle numberic input, enjoy!
Code:
#!/usr/bin/env python
import re
def nuput(prompt='Enter a number: '):
#Parses raw input depending on user input; this can be an int, float or
#Fraction. If an error occurs while parsing then raise a 'TypeError' for
#error catching purposes.
numbers = []
try:
for number in raw_input(prompt).split():
#Loops over each int, float and fraction in the raw_input()
#call and checks that the input fits given patterns.
if len(number)>1:
if re.search('^(\d+/\d+)$', number):
#Checks if the the string 'number' is a fraction.
#Unpacks numbers and assigns the values to 'one'
#and 'two'.
one, two = map(float, number.split('/'))
#Appends a string of float / float to 'numbers'.
numbers.append('%f/%f'%(one, two))
elif re.search('^(\d*\.\d+)$', number):
numbers.append(number)
elif number.isdigit(): numbers.append(number)
#Returns an evaluted expression of the values passed to nuput().
return eval('+'.join(numbers))
except:
raise TypeError, 'Input must be Decimal, Float or Fraction.'
Spot an error, let me know.
Mark.
Nice code Netytan  in colour
Last edited by netytan; March 22nd, 2004 at 08:01 PM.
March 22nd, 2004, 07:35 PM

Grim, when I ran your code I got an invalid syntax on the
if value<0:
It highlights the semicolon. I am running Python 2.3 if that has anything to do with it.
Also I hate to ask, but all this stuff looks latin to me. I understand the basic idea of these code snippets but maybe you could help me understand how to do my own. For future reference. Or atleast point me toward some reading material. That would be great.
Again, thanks for all yalls help.
March 22nd, 2004, 07:59 PM

The problem here is that devshed doesnt display html entities, change > with > and < with < and the program should
work fine .
Also, a good place to start would be the Python tutorial at Python.org, what exactly are you looking for?
Mark.
March 22nd, 2004, 08:16 PM

Okay, I tried fixing the code like you said but it gave me a semanic error. Anyway, I will just live with the decimal output because I am amazed that we could get the fractions working. As far as the tutorial goes. I know the basics of Python, so the beginners tutorials wont really help that much. I am looking for things that explain the stuff like:
Code:
imperial = re.compile(r"(\d*\s+\d/\d+)(\d/\d+)")
metric = re.compile(r"(\d*\.\d+)(\d+)(?!.*/)")
I have no clue what all those 'd's and 's's mean. I can kinda follow but not very ?comprehendably? Other than that I can follow an reproduce yalls code with the exception of some minor syntax errors because of stupid mistakes. The logic is fine.
Any pointers would be great, but that is what I am mainly confused on.
Thanks
March 23rd, 2004, 01:34 AM

Stupid me  just being too clever (I'm sure Dev Shed keeps changing it's encoding ). Anyway  code that works:
Code:
from __future__ import division
import re
imperial = re.compile(r"(\d*\s+\d/\d+)(\d/\d+)")
metric = re.compile(r"(\d*\.\d+)(\d+)(?!.*/)")
fractions = {}
fractions['0.00000'] = ''
for frac in [32, 16, 10, 8, 5, 4, 3, 2]:
for n in range(1, frac):
fractions["%0.5f"%(n/float(frac))] = "%s/%s"%(n, frac)
def NumericInput(prompt="Input value:"):
while True:
inp = raw_input(prompt).strip()
match = imperial.match(inp)
try:
if match:
val = match.group().replace(" ", "+", 1)
value = eval(val)
break
else:
match = metric.match(inp)
if match:
val = match.group()
value = eval(val)
break
except:
pass
print "Input format '1' , '1.5' or '1 1/2', retry..."
return value
def ImpPrint(value):
neg = ''
if value < 0:
neg = ''
value = value
integer = int(value)
remainder = "%0.5f"%(valueinteger)
if fractions.has_key(remainder):
return "%s%d %s"%(neg, integer, fractions[remainder])
else:
return "%s%d.%s"%(neg, integer, remainder)
q = NumericInput(" Number of doors: ")
w = NumericInput(" Width: ")
l = NumericInput(" Length: ")
c = NumericInput(" Cut depth: ")
m = NumericInput("Machine clearance: ")
s = NumericInput(" Stile width: ")
r = NumericInput(" Rail width: ")
sl = l
rl = w(2*s)+(2*c)
pl = sl(2*r)(2*c)(2*m)
pw = w(2*s)(2*c)(2*m)
rq = 2*q
sq = 2*q
pq = q
print "Cutlist for: %4d doors %8s x %8s"%(q, ImpPrint(w), ImpPrint(l))
print ""
print " Panels: %4d %8s x %8s"%(pq, ImpPrint(pw), ImpPrint(pl))
print " Stiles: %4d %8s x %8s"%(sq, ImpPrint(s), ImpPrint(sl))
print " Rails: %4d %8s x %8s"%(rq, ImpPrint(r), ImpPrint(rl))
Fo help on regular expressions:
http://www.amk.ca/python/howto/regex/
Cheers,
Grim
March 25th, 2004, 12:25 AM

Okay, sorry for not replying for a while. I have testing at school and I am kinda stressed out. Anywho. I have been playing around with the code and finally came up with one that works like I wanted. It is really sloppy though and I would like advice on how to clean it up.
Here it is:
Code:
from __future__ import division
import re
from string import *
fraction = re.compile(r"(\d*\s\d+/\d+)(\d+/\d+)")
metric = re.compile(r"(\d*\.\d+)(\d+)(?!.*/)")
def nunput(prompt="Input value:"):
inp = raw_input(prompt).strip()
frac = fraction.match(inp)
metr = metric.match(inp)
if frac:
val = frac.group().replace(" ", "+")
value = eval(val)
elif metr:
val = metr.group()
value = eval(val)
else:
print "Please use '1', '1.5', or '1 1/2' format only please."
return value
def f(value):
num = str(value).split('.')
le = len(num)
if le > 1:
l = len(num[1])
y = int('1'+('0'*l))
x = int(num[1])
n = str(num[0])
d = reduce(x, y)
else:
n = str(num[0])
d = ''
return n+' '+d
def reduce(x, y):
num1 = x
num2 = y
divisor = hcf(num1, num2)
while (divisor > 1):
num1 = num1 / divisor
num2 = num2 / divisor
divisor = hcf(num1, num2)
fnum1 = str(int(num1))
fnum2 = str(int(num2))
return fnum1+'/'+fnum2
def hcf(a, b):
num1 = a
num2 = b
if (num1 == 0 or num2 == 0):
return 0
remainder = num1 % num2
while (remainder != 0):
num1 = num2
num2 = remainder
remainder = num1 % num2
return num2
#Done
q = nunput(" Number of doors: ")
w = nunput(" Width: ")
l = nunput(" Length: ")
c = nunput(" Cut depth: ")
m = nunput("Machine clearance: ")
s = nunput(" Stile width: ")
r = nunput(" Rail width: ")
sl = l
rl = w(2*s)+(2*c)
pl = sl(2*r)(2*c)(2*m)
pw = w(2*s)(2*c)(2*m)
rq = 2*q
sq = 2*q
pq = q
print "Cutlist for:", q, "doors", f(w), "x", f(l)
print ""
print " Panels:", pq, '', f(pw), 'x', f(pl)
print " Stiles:", sq, '', f(s), 'x', f(sl)
print " Rails:", rq, '', f(r), 'x', f(rl)
Also feel free to use any of that code. Cause it took me forever to figure out how to get it to convert any decimal into a fraction and make a decimal a mixed fraction. Lord help. Well, like I said, any comments on this, good or bad, would be great.
Thanks.
Oops. One more thing. Is there anyway to get the output to line up in a chart or something. Since the length of the numbers vary I cant format my code to look right. Maybe there is something to do like center arrangement or matrices or something?
March 25th, 2004, 04:42 AM

You need a loop on your input  no point telling the user they have a problem if they can't fix it.
IMO a real world program will only output in standard fractions (like my example). Measure this with a ruler :
Code:
Number of doors: 1
Width: 12
Length: 12
Cut depth: 1/8
Machine clearance: 1/32
Stile width: 2 1/3
Rail width: 2 1/3
Cutlist for: 1 doors 12 x 12
Panels: 1  7 2083333333/100000000000 x 7 2083333333/100000000000
Stiles: 2  2 33333333333/100000000000 x 12
Rails: 2  2 33333333333/100000000000 x 7 58333333333/100000000000
But as this is your homework!!!, you don't need real world.
Use a formatted string to define the layout (like my example). Work out the longest answer and format the strings around it.
Credit where it's due :Fractions (int/int) question...?
Grim