Create if condition from string - python

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]

Related

How to remove leading zeros from the calculator expression in a string? python

I have a doubt, in python
the string is, Z = "00123+0567*29/03-7"
how to convert it to "123+567*29/3-7"
Even I tried with re.split('[+]|[*]|-|/', Z) later with for i in res : i = i.lstrip("0")
but it will split correctly, but to join back with the same operands as in the string "Z" as Z = "123+567*29/3-7"
How to resolve it
def cut_zeroes(Z):
i, res = 0, []
n = len(Z)
while i < n:
j = i
while i < n and Z[i] not in '+-/*':
i += 1
res.append(int(Z[j:i]))
if i < n:
res.append(Z[i])
i += 1
return ''.join(map(str,res))
Z = "00123+0567*29/03-700"
print(cut_zeroes(Z))
Z = "00123+0567*29/03-7"
print Z
import re
res = re.split(r'(\D)', Z)
print res
empty_lst = []
for i in res :
i = i.lstrip("0")
empty_lst.append(i)
print i
print empty_lst
new_str = ''.join(empty_lst)
print new_str
def zero_simplify(Z):
from re import sub
return [char for char in sub("0{2,}", "0", Z)]
Z = "00123+0567*29/03-7+0-000"
Z = zero_simplify(Z)
pos = len(Z)-1
while pos>-1:
if Z[pos]=="0":
end = pos
while Z[pos] == "0":
pos-=1
if pos==-1:
del Z[pos+1:end+1]
if (not Z[pos].isdigit()) and (Z[pos] != ".") and (Z[pos] == "0"):
del Z[pos+1:end+1]
else:
pos-=1
Z = "".join(Z)
print(Z)
What this does is set Z, 'listify' it, and set pos to the last position in Z. Then it removes all of the unnecessary 0s using the loop and Z = "".join(Z). Then it prints Z at the end. If you want a function to remove the zeroes, you can have it this way:
def zero_simplify(Z):
from re import sub
return [char for char in sub("0{2,}", "0", Z)]
def remove_unnecessary_zeroes(Z):
Z = [char for char in Z]
pos = len(Z)-1
while pos>-1:
if Z[pos]=="0":
end = pos
while Z[pos] == "0":
pos-=1
if pos==-1:
del Z[pos+1:end+1]
if (not Z[pos].isdigit()) and (Z[pos] != ".") and (Z[pos] == "0"):
del Z[pos+1:end+1]
else:
pos-=1
Z = "".join(Z)
return Z
Z = "00123+0567*29/03-7+0-000"
print(remove_unnecessary_zeroes(Z))
Try it yourself and tell me in the comments if it worked for you!
Here's a concise (if you strip all the comment in the code) and elegant way to achieve this:
import re
Z = "00123+0567*29/03-7"
operators = re.findall('\D', Z) # List all the operators used in the string
nums = re.split('\D', Z) # List all the numbers in the list
operators.append('') # Add an empty operator at the end
nums = [num.lstrip('0') for num in nums] # Strip all the leading zeroes from each numbers
# Create a list with the operands (numbers) concatenated by operators
num_operator_list = [nums[i] + operators[i] for i in range(len(nums))]
# Join all the intermediate expressions to create a final expression
final_expression = ''.join(num_operator_list)
print(final_expression)
Output
123+567*29/3-7
Explanation
First of all you need to separate the operators and the operands and then lstrip the zeroes from each operands. After this add an additional empty operator at the end of operator list. Then concatenate each operand with corresponding operator (the empty operator is concatenated with the last operand). Finally, join the list to get a final expression.
It can be done with regular expressions:
import re
Z = "00123+0567*29/03-7"
r1=r"(\D)0+(\d+)"
r2=r"\b0+(\d+)"
#substitute non-digit,leading zeroes, digits with non-digit and digits
sub1=re.sub(r1,r"\1\2",Z)
#substitute start of string, leading zeroes, digits with digits
sub2=re.sub(r2,r"\1",sub1)
print(sub2)
It is done in two passes (to handle the leading zeroes in the beginning of the string), I don't know if it's possible to do in one pass.

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

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")))

Sort string with integers and words without any change in their positions

Say I have a string a.
a = "12 I have car 8 200 a"
I need to sort this string in such a way that the output should be
8 a car have 12 200 I
ie, Sort the string in such a way that all words are in alphabetical order and all integers are in numerical order. Furthermore, if the nth element in the string is an integer it must remain an integer, and if it is a word it must remain a word.
This is what I tried.
a = "12 I have car 8 200 a"
def is_digit(element_):
"""
Function to check the item is a number. We can make using of default isdigit function
but it will not work with negative numbers.
:param element_:
:return: is_digit_
"""
try:
int(element_)
is_digit_ = True
except ValueError:
is_digit_ = False
return is_digit_
space_separated = a.split()
integers = [int(i) for i in space_separated if is_digit(i)]
strings = [i for i in space_separated if i.isalpha()]
# sort list in place
integers.sort()
strings.sort(key=str.lower)
# This conversion to iter is to make use of next method.
int_iter = iter(integers)
st_iter = iter(strings)
final = [next(int_iter) if is_digit(element) else next(st_iter) if element.isalpha() else element for element in
space_separated]
print " ".join(map(str, final))
# 8 a car have 12 200 I
I am getting the right output. But I am using two separate sorting function for sorting integers and the words(which I think is expensive).
Is it possible to do the entire sorting using a single sort function?.
numpy allows to write it more concisely, though doesn't eliminate the need for two separate sorts:
$ python3
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> from numpy.core.defchararray import isdecimal, lower
>>>
>>> s = "12 I have car 8 200 a"
>>>
>>> a = np.array(s.split())
>>>
>>> integer_mask = isdecimal(a)
>>> string_mask = ~integer_mask
>>> strings = a[string_mask]
>>>
>>> a[integer_mask] = np.sort(np.int_(a[integer_mask]))
>>> a[string_mask] = strings[np.argsort(lower(strings))]
>>>
>>> ' '.join(a)
'8 a car have 12 200 I'
Is it possible to do the entire sorting using a single sort function?.
No, not really.
Why not? It turns out the answer is already in your code.
integers.sort()
strings.sort(key=str.lower)
Notice how you need to sort by two different functions here. The first is an integer sort, the second is a lowercase string sort. We could try something like this:
def get_sort_order(element):
try:
value = int(element)
except ValueError:
value = element.lower()
return value
a.sort(key=get_sort_order)
But that doesn't work either; it just gives us the result
['8', '12', '200', 'a', 'car', 'have', 'I']
You could probably force this into a solution, but it isn't going to be pretty.
However, there is another point I'd like to address:
But I am using two separate sorting function for sorting integers and the words (which I think is expensive).
Sorting two distinct lists is basically always going to be faster anyway. To find out why, just look at the time complexity of the two tasks:
Assuming a list of length 1000, exactly half integer and half strings, and a sorting algorithm of O(nlog(n)):
One single sort: 1000 * log(1000) = 3000
Two separate sorts: 2 * (500 * log(500) = ~2699
So sorting the list in a single run is both more difficult to implement and slower!
It is possible in one sort, by applying a custom function within the 'sorted' method as a User described above. I have tried a simplified version for the same. The default 'sorted' method does the wonder with a little tweak. Hope it resolves your query.
import re
input = "12 I have car 8 200 a"
splitted = input.split()
s_lst=sorted(splitted, key=lambda a:int(a) if a.isdigit() else a.lower())
count_nos = re.findall(r'\d+',' '.join(s_lst))
str_index = len(count_nos)
no_index = 0
result=[]
for i in range(0,len(splitted)):
if splitted[i].isdigit():
result.append(s_lst[no_index])
no_index+=1
else:
result.append(s_lst[str_index])
str_index+=1
print ' '.join(result)
You could do in one sort provided you write a custom function for comparision.
The idea is to sort the words in ascending order and integer in descending order in the same list . Incase of word and integer is compared then treat the word as smaller compared to word.
And then for printing the final result increment the index for word if a word is found , decrement the index for integer if digit is found.
The below code works in python2:
a = "12 I have car 8 200 a"
def custom_compare(x,y):
if x.isdigit() and y.isdigit():
return int(y) - int(x) #do a descending order
if x.isdigit() and y.isdigit() == False:
return 1
if x.isdigit() == False and y.isdigit():
return -1
if x.isdigit() == False and y.isdigit() == False:
# do ascending order
if x.lower() == y.lower():
return 0
elif x.lower() < y.lower():
return -1
else:
return 1
original_list = a.split(" ")
sorted_list = sorted(original_list, cmp=custom_compare)
result = []
integer_index = -1
string_index = 0
for word in original_list:
if word.isdigit():
result.append(sorted_list[integer_index])
integer_index = integer_index - 1
else:
result.append(sorted_list[string_index])
string_index = string_index + 1
result
['8', 'a', 'car', 'have', '12', '200', 'I']
Python 3:
import functools
a = "12 I have car 8 200 a"
def custom_compare(x,y):
if x.isdigit() and y.isdigit():
return int(y) - int(x) #do a descending order
if x.isdigit() and y.isdigit() == False:
return 1
if x.isdigit() == False and y.isdigit():
return -1
if x.isdigit() == False and y.isdigit() == False:
# do ascending order
if x.lower() == y.lower():
return 0
elif x.lower() < y.lower():
return -1
else:
return 1
original_list = a.split(" ")
sorted_list = sorted(original_list, key=functools.cmp_to_key(custom_compare))
result = []
integer_index = -1
string_index = 0
for word in original_list:
if word.isdigit():
result.append(sorted_list[integer_index])
integer_index = integer_index - 1
else:
result.append(sorted_list[string_index])
string_index = string_index + 1
result
PS:The word comparison could be efficiently written.
I am from C background and I am not sure of the pythonic way of comparison.
s = "2 is a A -3 car 11 I 0 a"
def magick(s):
s = s.split()
def reverse(tuples):
return [(a, b) for (b, a) in tuples]
def do_sort(tuples):
firsts = [a for a, _ in tuples]
seconds = [a for _, a in tuples]
return list(zip(sorted(firsts), seconds))
def str_is_int(x):
try:
int(x)
return True
except:
return False
indexed = list(enumerate(s))
ints = do_sort([(int(x), ix) for (ix, x) in indexed if str_is_int(x)])
strs = do_sort([( x , ix) for (ix, x) in indexed if not str_is_int(x)])
return ' '.join([str(b) for _, b in sorted(reverse(ints+strs))])
print(magick(s))
This solution utilizes a single custom sorting algorithm, after grouping the original input into integers and strings:
def gt(a, b):
return a > b if isinstance(a, int) and isinstance(b, int) else a[0].lower() > b[0].lower()
def type_sort(d):
'''similar to bubble sort, but does not swap elements of different types.
For instance, type_sort([5, 3, 'b', 'a']) => [3, 5, 'a', 'b']
'''
for _ in d:
for i in range(len(d)-1):
_c = d[i]
_t = d[i+1]
if isinstance(_c, type(_t)):
if gt(_c, _t):
d[i+1] = _c
d[i] = _t
return d
def get_type(x):
return int(x) if x.isdigit() else x
def sort_in_place(s:str):
_s = list(map(get_type, s.split()))
new_s = type_sort([i for i in _s if isinstance(i, int)]+[i for i in _s if isinstance(i, str)])
ints = iter(i for i in new_s if isinstance(i, int))
strings = iter(i for i in new_s if isinstance(i, str))
return ' '.join(map(str, [next(ints) if isinstance(i, int) else next(strings) for i in _s]))
print(sort_in_place(a))
Output:
'8 a car have 12 200 I'

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.

Compare strings in list in Python and output character until they are identical

How can I compare all strings in a list e.g:
"A-B-C-D-E-F-H-A",
"A-B-C-F-G-H-M-P",
And output until which character they are identical:
In the example above it would be:
Character 6
And output the most similar strings.
I tried with collections.Counter but that did not work.
You're trying to go character by character in the two strings in lockstep. This is a job for zip:
A = "A-B-C-D-E-F-H-A"
B = "A-B-C-F-G-H-M-P"
count = 0
for a, b in zip(A, B):
if a == b:
count += 1
else:
break
Or, if you prefer "…as long as they are…" is a job for takewhile:
from itertools import takewhile
from operator import eq
def ilen(iterable): return sum(1 for _ in iterable)
count = ilen(takewhile(lambda ab: eq(*ab), zip(A, B)))
If you have a list of these strings, and you want to compare every string to every other string:
First, you turn the above code into a function. I'll do it with the itertools version, but you can do it with the other just as easily:
def shared_prefix(A, B):
return ilen(takewhile(lambda ab: eq(*ab), zip(A, B)))
Now, for every string, you compare it to all the rest of the strings. There's an easy way to do it with combinations:
from itertools import combinations
counts = [shared_prefix(pair) for pair in combinations(list_o_strings, 2)]
But if you don't understand that, you can write it as a nested loop. The only tricky part is what "the rest of the strings" means. You can't loop over all the strings in both the outer and inner loops, or you'll compare each pair of strings twice (once in each order), and compare each string to itself. So it has to mean "all the strings after the current one". Like this:
counts = []
for i, s1 in enumerate(list_o_strings):
for s2 in list_o_strings[i+1:]:
counts.append(prefix(s1, s2))
I think this code will solve your problem.
listA = "A-B-C-D-E-F-H-A"
listB = "A-B-C-F-G-H-M-P"
newListA = listA.replace ("-", "")
newListB = listB.replace ("-", "")
# newListA = "ABCDEFHA"
# newListB = "ABCFGHMP"
i = 0
exit = 0
while ((i < len (newListA)) & (exit == 0)):
if (newListA[i] != newListB[i]):
exit = 1
i = i + 1
print ("Character: " + str(i))

Categories