Evaluating a string of operations with standard python library no eval() function - python

I need a short function to return the answer to a string of multiplication/addition with pemdas. For example it should take "6*3+4" and return 22 or "7+3*10" and return 37. Ideally it could easily be changed to include division/subtraction.
I've tried doing this with index operations.
def pemdas(s):
mult = "*"
add = "+"
mi = s.index(mult)
res = int(s[mi-1])*int(s[mi+1])
s = s[0:mi-1:]+s[mi+2::]
s = s.replace(add,"")
res = res + int(s)
return res
Works with 1st test case but not the second. Unfortunately this fails with any double digit integer inputs. Is there a simple way of doing this without eval() and just the standard library?

You can write a simple parser:
import operator, re
ops = {'+':operator.add, '-':operator.sub, '*':operator.mul, '/':operator.truediv}
def _eval(tokens):
a, *b = tokens
if not b:
return int(a)
if b[0] in {'*', '/'}:
while b and b[0] in {'*', '/'}:
a = ops[b[0]](int(a), int(b[1]))
b = b[2:]
return a if not b else ops[b[0]](a, _eval(b[1:]))
return ops[b[0]](int(a), _eval(b[1:]))
print(_eval(re.findall('\d+|[\+\*\-/]', "2*3*4+1")))
print(_eval(re.findall('\d+|[\+\*\-/]', "6+3*4")))
print(_eval(re.findall('\d+|[\+\*\-/]', "7*3+10")))
print(_eval(re.findall('\d+|[\+\*\-/]', "1+2*3*4+5")))
Output:
25
18
31
30
Edit: solution without re or operator:
def tokenize(stream):
l, s = [], ''
for i in stream:
if i.isdigit():
s += i
else:
l.append(s)
l.append(i)
s = ''
return l+[s]
ops = {'+':lambda x, y:x+y, '-':lambda x, y:x-y, '*':lambda x, y:x*y, '/':lambda x, y:x/float(y)}
...
Now, to evaluate:
print(_eval(tokenize("6+3*4")))

Related

I am trying to create a function that inputs a name and outputs a rank. what is the best way to create that function?

I am trying to create function that takes an input name and outputs a rank based on the order of the letters it has for example a=1 b=2
name = ab
rank = 3
import string
x = "richard"
y = "donald"
c = "Sam"
numbers = []
for i in range(1,27):
numbers.append(i)
print(numbers)
alphabet_string = string.ascii_lowercase
alphabet_list = list(alphabet_string)
print(alphabet_list)
new_x = list(x)
new_y = list(y)
new_c = list(c)
zip_iterators = zip(alphabet_list,numbers)
dic = list(zip_iterators)
print(dic)
def rank(name):
rank = 0
for l in range(0,len(name)):
for k,v in dic:
if l == k:
v += rank
print(rank)
rank(new_c)
but I failed so far
letter_rank = {letter:rank for rank, letter in enumerate(string.ascii_lowercase, 1)}
def rank(name):
return sum(letter_rank.get(c, 0) for c in name.lower())
You can just use the ascii_lowercase constant in the string module:
from string import ascii_lowercase
def rank(x):
total = 0
for char in x:
if char in ascii_lowercase:
total += ascii_lowercase.index(char) + 1
return total
print(rank('abc'))
Output: 6
You could use ascii_lowercase.index(letter) or create a dictionary to lookup the rank of a letter. (My example doesnt't include caps, but you could do so if you wish)
lookup with dictionary
from string import ascii_lowercase
alphabet_lookup = {letter:i+1 for i, letter in enumerate(ascii_lowercase)}
def rank(name):
return sum(alphabet_lookup[c] for c in name.lower())
print(rank('baa')) # outputs 4
str.index
def rank(name):
return sum(ascii_lowercase.index(c)+1 for c in name.lower())
You can use ord() built-in function and list comprehension to create the function you need as follows:
x = "Villalobos Velasquez Santiago"
def fun(s):
out = 0
for i in s.lower():
if i != " ":
out += (ord(i)-96)
return out
print(fun(x))
Output:
333

Create if condition from string

i have a graphical interface where a user could enter a datafilter as a string, like:
>= 10 <= 100
I like to create an if-condition from this string.
The code i currently got splits the string into a list:
import re
def filter_data(string_filter)
# \s actually not necessary, just for optical reason
s_filter = r'(\s|>=|>|<=|<|=|OR|AND)'
splitted_filter = re.split(s_filter, string_filter)
splitted_filter = list(filter(lambda x: not (x.isspace()) and x, splitted_filter))
print(splitted_filter)
with the given filter string above, the output would be:
['>=', '10', '<=', '100']
I now like to use this to create the if-condition of it.
My current idea would be to create nested if-statements.
Do you see a better solution?
Thanks!
Handle the operations with functions instead of control flow syntax constructs.
For example:
from operator import ge
binary_operations = {
">=": ge,
...
}
splitted_filter = ...
x = ...
result = True
while result and splitted_filter:
op = splitted_filter.pop(0)
func = binary_operations[op]
rhs = splitted_filter.pop(0)
result = func(x, rhs):
if result:
# do stuff
I would probably create a dict of operator to function. For example:
operators = {
'>=': lambda a, b: a >= b,
'<=': lambda a, b: a <= b,
}
Then you can start composing those functions together.
First, to iterate by pairs:
def pairs(l):
assert len(l) % 2 == 0
for i in range(0, len(l), 2):
yield (l[i], l[i + 1])
Now, apply that to your filter list and build a list of functions:
filters_to_apply = []
for op, value in pairs(splitted_filter):
def filter(record):
return operators[op](record, value)
filters_to_apply.append(filter)
Finally, apply those filters to your data:
if all(f(record) for f in filters_to_apply):
# Keep the current record
Yes, you can string conditions together with and.
Instead of
if x >= 10:
if x <= 100:
[do stuff]
You can generate
if x >= 10 and x <= 100:
[do stuff]

Printing alphabets advanced by n in Python

how can i write a python program to intake some alphabets in and print out (alphabets+n) in the output. Example
my_string = 'abc'
expected_output = 'cde' # n=2
One way I've thought is by using str.maketrans, and mapping the original input to (alphabets + n). Is there any other way?
PS: xyz should translate to abc
I've tried to write my own code as well for this, (apart from the infinitely better answers mentioned):
number = 2
prim = """abc! fgdf """
final = prim.lower()
for x in final:
if(x =="y"):
print("a", end="")
elif(x=="z"):
print("b", end="")
else:
conv = ord(x)
x = conv+number
print(chr(x),end="")
Any comments on how to not convert special chars? thanks
If you don't care about wrapping around, you can just do:
def shiftString(string, number):
return "".join(map(lambda x: chr(ord(x)+number),string))
If you do want to wrap around (think Caesar chiffre), you'll need to specify a start and an end of where the alphabet begins and ends:
def shiftString(string, number, start=97, num_of_symbols=26):
return "".join(map(lambda x: chr(((ord(x)+number-start) %
num_of_symbols)+start) if start <= ord(x) <= start+num_of_symbols
else x,string))
That would, e.g., convert abcxyz, when given a shift of 2, into cdezab.
If you actually want to use it for "encryption", make sure to exclude non-alphabetic characters (like spaces etc.) from it.
edit: Shameless plug of my Vignère tool in Python
edit2: Now only converts in its range.
How about something like
>>> my_string = "abc"
>>> n = 2
>>> "".join([ chr(ord(i) + n) for i in my_string])
'cde'
Note As mentioned in comments the question is bit vague about what to do when the edge cases are encoundered like xyz
Edit To take care of edge cases, you can write something like
>>> from string import ascii_lowercase
>>> lower = ascii_lowercase
>>> input = "xyz"
>>> "".join([ lower[(lower.index(i)+2)%26] for i in input ])
'zab'
>>> input = "abc"
>>> "".join([ lower[(lower.index(i)+2)%26] for i in input ])
'cde'
I've made the following change to the code:
number = 2
prim = """Special() ops() chars!!"""
final = prim.lower()
for x in final:
if(x =="y"):
print("a", end="")
elif(x=="z"):
print("b", end="")
elif (ord(x) in range(97, 124)):
conv = ord(x)
x = conv+number
print(chr(x),end="")
else:
print(x, end="")
**Output**: urgekcn() qru() ejctu!!
test_data = (('abz', 2), ('abc', 3), ('aek', 26), ('abcd', 25))
# translate every character
def shiftstr(s, k):
if not (isinstance(s, str) and isinstance(k, int) and k >=0):
return s
a = ord('a')
return ''.join([chr(a+((ord(c)-a+k)%26)) for c in s])
for s, k in test_data:
print(shiftstr(s, k))
print('----')
# translate at most 26 characters, rest look up dictionary at O(1)
def shiftstr(s, k):
if not (isinstance(s, str) and isinstance(k, int) and k >=0):
return s
a = ord('a')
d = {}
l = []
for c in s:
v = d.get(c)
if v is None:
v = chr(a+((ord(c)-a+k)%26))
d[c] = v
l.append(v)
return ''.join(l)
for s, k in test_data:
print(shiftstr(s, k))
Testing shiftstr_test.py (above code):
$ python3 shiftstr_test.py
cdb
def
aek
zabc
----
cdb
def
aek
zabc
It covers wrapping.

Python, context sensitive string substitution

Is it possible to do something like this in Python using regular expressions?
Increment every character that is a number in a string by 1
So input "123ab5" would become "234ab6"
I know I could iterate over the string and manually increment each character if it's a number, but this seems unpythonic.
note. This is not homework. I've simplified my problem down to a level that sounds like a homework exercise.
a = "123ab5"
b = ''.join(map(lambda x: str(int(x) + 1) if x.isdigit() else x, a))
or:
b = ''.join(str(int(x) + 1) if x.isdigit() else x for x in a)
or:
import string
b = a.translate(string.maketrans('0123456789', '1234567890'))
In any of these cases:
# b == "234ab6"
EDIT - the first two map 9 to a 10, the last one wraps it to 0. To wrap the first two into zero, you will have to replace str(int(x) + 1) with str((int(x) + 1) % 10)
>>> test = '123ab5'
>>> def f(x):
try:
return str(int(x)+1)
except ValueError:
return x
>>> ''.join(map(f,test))
'234ab6'
>>> a = "123ab5"
>>> def foo(n):
... try: n = int(n)+1
... except ValueError: pass
... return str(n)
...
>>> a = ''.join(map(foo, a))
>>> a
'234ab6'
by the way with a simple if or with try-catch eumiro solution with join+map is the more pythonic solution for me too

removing non-numeric characters from a string

strings = ["1 asdf 2", "25etrth", "2234342 awefiasd"] #and so on
Which is the easiest way to get [1, 25, 2234342]?
How can this be done without a regex module or expression like (^[0-9]+)?
One could write a helper function to extract the prefix:
def numeric_prefix(s):
n = 0
for c in s:
if not c.isdigit():
return n
else:
n = n * 10 + int(c)
return n
Example usage:
>>> strings = ["1asdf", "25etrth", "2234342 awefiasd"]
>>> [numeric_prefix(s) for s in strings]
[1, 25, 2234342]
Note that this will produce correct output (zero) when the input string does not have a numeric prefix (as in the case of empty string).
Working from Mikel's solution, one could write a more concise definition of numeric_prefix:
import itertools
def numeric_prefix(s):
n = ''.join(itertools.takewhile(lambda c: c.isdigit(), s))
return int(n) if n else 0
new = []
for item in strings:
new.append(int(''.join(i for i in item if i.isdigit())))
print new
[1, 25, 2234342]
Basic usage of regular expressions:
import re
strings = ["1asdf", "25etrth", "2234342 awefiasd"]
regex = re.compile('^(\d*)')
for s in strings:
mo = regex.match(s)
print s, '->', mo.group(0)
1asdf -> 1
25etrth -> 25
2234342 awefiasd -> 2234342
Building on sahhhm's answer, you can fix the "1 asdf 1" problem by using takewhile.
from itertools import takewhile
def isdigit(char):
return char.isdigit()
numbers = []
for string in strings:
result = takewhile(isdigit, string)
resultstr = ''.join(result)
if resultstr:
number = int(resultstr)
if number:
numbers.append(number)
So you only want the leading digits? And you want to avoid regexes? Probably there's something shorter but this is the obvious solution.
nlist = []
for s in strings:
if not s or s[0].isalpha(): continue
for i, c in enumerate(s):
if not c.isdigit():
nlist.append(int(s[:i]))
break
else:
nlist.append(int(s))

Categories