Reputation: 4940
I am teaching some neighborhood kids to program in Python. Our first project is to convert a string given as a Roman numeral to the Arabic value.
So we developed an function to evaluate a string that is a Roman numeral the function takes a string and creates a list that has the Arabic equivalents and the operations that would be done to evaluate to the Arabic equivalent.
For example suppose you fed in XI the function will return [1,'+',10] If you fed in IX the function will return [10,'-',1] Since we need to handle the cases where adjacent values are equal separately let us ignore the case where the supplied value is XII as that would return [1,'=',1,'+',10] and the case where the Roman is IIX as that would return [10,'-',1,'=',1]
Here is the function
def conversion(some_roman):
roman_dict = {'I':1,'V':5,'X':10,'L':50,'C':100,'D':500,'M',1000}
arabic_list = []
for letter in some_roman.upper():
if len(roman_list) == 0:
arabic_list.append(roman_dict[letter]
continue
previous = roman_list[-1]
current_arabic = roman_dict[letter]
if current_arabic > previous:
arabic_list.extend(['+',current_arabic])
continue
if current_arabic == previous:
arabic_list.extend(['=',current_arabic])
continue
if current_arabic < previous:
arabic_list.extend(['-',current_arabic])
arabic_list.reverse()
return arabic_list
the only way I can think to evaluate the result is to use eval()
something like
def evaluate(some_list):
list_of_strings = [str(item) for item in some_list]
converted_to_string = ''.join([list_of_strings])
arabic_value = eval(converted_to_string)
return arabic_value
I am a little bit nervous about this code because at some point I read that eval is dangerous to use in most circumstances as it allows someone to introduce mischief into your system. But I can't figure out another way to evaluate the list returned from the first function. So without having to write a more complex function.
The kids get the conversion function so even if it looks complicated they understand the process of roman numeral conversion and it makes sense. When we have talked about evaluation though I can see they get lost. Thus I am really hoping for some way to evaluate the results of the conversion function that doesn't require too much convoluted code.
Sorry if this is warped, I am so . . .
Upvotes: 4
Views: 3106
Reputation: 1
# Luna calculator V2.0
print("!! You can use p as 3.141592 or e as 2.718281 !!")
import math
def checking_operator(first_input):
# checking operators part
list_number = []
list_operator = []
temporary = ""
already_operate = 0
for first_check in enumerate(first_input,0):
if (first_check[1] in '+-*/.') and ( first_input[first_check[0]+1] in '+-/*.' and first_input[first_check[0]+1] not in "-" ) or (first_input[-1] in '+-*/'):
print("!! Your equation is incomplete or wrong !!")
print()
return False
if first_check[1] == "-" and first_check[0] == 0:
temporary += first_check[1]
elif first_check[1].isdigit() == True or first_check[1] == "." or (first_check[1] == "p" or first_check[1] == "e") :
if first_check[1] == "p":
temporary += str(math.pi)
already_operate = 0
elif first_check[1] == "e":
temporary += str(math.e)
already_operate = 0
else:
temporary += first_check[1]
already_operate = 0
if first_check[0] == (len(first_input) - 1):
list_number.append(float(temporary))
temporary = ""
elif (first_check[1] == "-") and (already_operate == 1):
temporary += "-"
already_operate = 0
elif first_check[1] in '+-*/':
list_operator.append(first_check[1])
already_operate = 1
if temporary.isdigit() == True or temporary[-1].isdigit() == True:
list_number.append(float(temporary))
temporary = ""
return list_number,list_operator
def group(af_c):
list_number,list_operator = af_c[0],af_c[1]
group_main = []
temp_list = []
grouping = 0
#grouping part
for operator in range(0,len(list_operator)): # range(0,12) = 0,1,2,...,11
if list_operator[operator] == "*" or list_operator[operator] == "/":
temp_list.append(list_number[operator])
grouping = 1
if grouping == 1 and (list_operator[operator] == "+" or list_operator[operator] == "-"):
temp_list.append(list_number[operator])
group_main.append(temp_list)
temp_list = []
grouping = 0
elif grouping == 0 and (list_operator[operator] == "+" or list_operator[operator] == "-"):
group_main.append(list_number[operator])
if operator == len(list_operator)-1 and grouping == 0:
group_main.append(list_number[operator+1])
if grouping == 1 and operator == len(list_operator) - 1:
temp_list.append(list_number[operator+1])
group_main.append(temp_list)
return group_main,list_operator
#print(group_main)
def calculation(af_g):
# calculation part
group_main = af_g[0]
list_operator = af_g[1]
group_main_2 = []
operating = 0
k = 0
for from_group_main in enumerate(group_main,0): # from_group_main = (2 , [3,5,3])
if type(from_group_main[1]) == list: # [3,5,3]
temp_number = 1
for number in enumerate(from_group_main[1],0): # from_group_main[1] = [3,5,3] , number{round 1} = (0, 3) ; number[1] == 3
if number[0] == len(from_group_main[1]) - 1:
group_main_2.append(temp_number)
temp_number = 1
operating = 0
break
elif (list_operator[from_group_main[0]+k] == "*" or list_operator[from_group_main[0]+k] == "/") and operating == 0:
if list_operator[from_group_main[0]+k] == "*":
temp_number *= number[1] * from_group_main[1][number[0]+1]
if list_operator[from_group_main[0]+k] == "/":
temp_number = 0
try:
temp_number += number[1] / from_group_main[1][number[0]+1]
except Exception:
break
operating = 1
elif (list_operator[from_group_main[0]+k] == "*" or list_operator[from_group_main[0]+k] == "/") and operating == 1:
if list_operator[from_group_main[0]+k] == "*":
temp_number *= from_group_main[1][number[0]+1]
if list_operator[from_group_main[0]+k] == "/":
try:
temp_number /= from_group_main[1][number[0]+1]
except Exception:
break
k += 1
else:
group_main_2.append(from_group_main[1])
return group_main_2,list_operator
def if_all_operator(af_cal): # af_cal = tuple( group_main_2 , list_operator )
group_main_2 = af_cal[0]
list_operator = af_cal[1]
last_operator = [j for j in list_operator if j == "+" or j == "-"]
#print(group_main_2)
final,plus_minius = 0,0
#print(last_operator)
# summation part
for j2 in range(0,len(group_main_2)): # 0 - 6
if j2 == len(group_main_2)-1:
break
if last_operator[j2] == "+" and plus_minius == 0:
final = group_main_2[j2] + group_main_2[j2+1]
plus_minius = 1
elif last_operator[j2] == "-" and plus_minius == 0:
final = group_main_2[j2] - group_main_2[j2+1]
plus_minius = 1
elif last_operator[j2] == "+" and plus_minius != 0:
final += group_main_2[j2+1]
elif last_operator[j2] == "-" and plus_minius != 0:
final -= group_main_2[j2+1]
print("=",final)
while 1: # main program
skip = False
start = input("Calculate: ")
for starting in start:
if (starting not in '+-*/.' and (starting != "p" and starting != "e") and starting.isdigit() != True) or (start[0] in '+*/.'):
skip = True
print("Sorry I don't know that :(")
after_check = False
print()
break
else:
after_check = checking_operator(start)
break
if skip == False:
try:
float(start)
print(f"= {start}")
continue
except ValueError:
if start.isdigit() == True:
print(f"= {start}")
continue
elif start == "p":
print(f"= {math.pi}")
continue
elif start == "e":
print(f"= {math.e}")
continue
#print("af_check",after_check)
divide_by_zero = 0
if after_check != False:
if 0.0 in after_check[0] and "/" in after_check[1]:
for zero in range(0,len(after_check[0])-1):
if after_check[1][zero] == "/" and after_check[0][zero+1] == 0.0:
divide_by_zero = 1
if zero == len(after_check[0]) - 1 and divide_by_zero == True:
break
if divide_by_zero == 0 and after_check != False:
after_group = group(after_check)
if divide_by_zero == 0 and after_check != False:
after_calculation = calculation(after_group)
if divide_by_zero == 0 and after_check != False:
if ("+") not in after_calculation[1] and ("-") not in after_calculation[1]:
print("=",after_calculation[0][0])
else:
after_calculation = if_all_operator(after_calculation)
if divide_by_zero == 1:
print("!! Divide by zero detected !!")
print()
Upvotes: 0
Reputation: 19030
The only implementation of a safe expression evalulator that I've come across is:
It supports a lot of basic Python-ish expressions and is quite restricted in what it allows you to do (so you don't blow up the interpreter or do something evil). It uses the python ast
module for parsing, and evaluates the result itself.
Example:
from simpleeval import simple_eval
simple_eval("21 + 21")
Then you can extend it and give it access to the parts of your program that you want to:
simple_eval("x + y", names={"x": 22, "y": 48})
or
simple_eval("do_thing(11)", functions={"do_thing": my_callback})
and so on.
Upvotes: 1
Reputation: 309831
Is there a way to accomplish what eval does without using eval
Yes, definitely. One option would be to convert the whole thing into an ast tree and parse it yourself (see here for an example).
I am a little bit nervous about this code because at some point I read that eval is dangerous to use in most circumstances as it allows someone to introduce mischief into your system.
This is definitely true. Any time you consider using eval, you need to do some thinking about your particular use-case. The real question is how much do you trust the user and what damage can they do? If you're distributing this as a script and users are only using it on their own computer, then it's really not a problem -- After all, they don't need to inject malicious code into your script to remove their home directory. If you're planning on hosting this on your server, that's a different story entirely ... Then you need to figure out where the string comes from and if there is any way for the user to modify the string in a way that could make it untrusted to run. Hackers are pretty clever1,2 and so hosting something like this on your server is generally not a good idea. (I always assume that the hackers know python WAY better than I do).
1http://blog.delroth.net/2013/03/escaping-a-python-sandbox-ndh-2013-quals-writeup/
2http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
Upvotes: 6