So I've got a string that looks like "012 + 2 - 01 + 24" for example. I want to be able to quickly (less code) evaluate that expression...
I could use eval() on the string, but I don't want 012 to be represented in octal form (10), I want it to be represented as an int (12).
My solution for this works, but it is not elegant. I am sort of assuming that there is a really good pythonic way to do this.
My solution:
#expression is some string that looks like "012 + 2 - 01 + 24"
atomlist = []
for atom in expression.split():
if "+" not in atom and "-" not in atom:
atomlist.append(int(atom))
else:
atomlist.append(atom)
#print atomlist
evalstring = ""
for atom in atomlist:
evalstring+=str(atom)
#print evalstring
num = eval(evalstring)
Basically, I tear appart the string, and find numbers in it and turn them into ints, and then I rebuild the string with the ints (essentially removing leading 0's except where 0 is a number on its own).
How can this be done better?
I'd be tempted to use regular expressions to remove the leading zeroes:
>>> re.sub(r'\b0+(?!\b)', '', '012 + 2 + 0 - 01 + 204 - 0')
'12 + 2 + 0 - 1 + 204 - 0'
This removes zeroes at the start of every number, except when the number consists entirely of zeroes:
the first \b matches a word (token) boundary;
the 0+ matches one or more consecutive zeroes;
the (?!\b) (negative lookahead) inhibits matches where the sequence of zeroes is followed by a token boundary.
One advantage of this approach over split()-based alternatives is that it doesn't require spaces in order to work:
>>> re.sub(r'\b0+(?!\b)', '', '012+2+0-01+204-0')
'12+2+0-1+204-0'
You can do this in one line using lstrip() to strip off any leading zeros:
>>> eval("".join(token.lstrip('0') for token in s.split()))
37
I'd like to do it this way:
>>> s = '012 + 2 + 0 - 01 + 204 - 0'
>>> ' '.join(str(int(x)) if x.isdigit() else x for x in s.split())
'12 + 2 + 0 - 1 + 204 - 0'
Use float() if you want to handle them too :)
int does not assume that a leading zero indicates an octal number:
In [26]: int('012')
Out[26]: 12
Accordingly, you can safely evalute the expression with the following code
from operator import add, sub
from collections import deque
def mapper(item, opmap = {'+': add, '-': sub}):
try: return int(item)
except ValueError: pass
return opmap[item]
stack = deque()
# if item filters out empty strings between whitespace sequences
for item in (mapper(item) for item in "012 + 2 - 01 + 24".split(' ') if item):
if stack and callable(stack[-1]):
f = stack.pop()
stack.append(f(stack.pop(), item))
else: stack.append(item)
print stack.pop()
Not a one-liner, but it is safe, because you control all of the functions which can be executed.
Related
I have a range of string such as: "024764108", "002231531", "005231329", they have exactly 9 digits. And I want to add - to each group of 3 digits. The result I want is as below:
"024-764-108", "002-231-531", "005-231-329".
How can I explain my think to python?
Here is a dynamic solution:
In [41]: df
Out[41]:
num
0 024764108
1 002231531
2 005231329
3 012345678901234
In [42]: df.num.str.extractall(r'(\d{3})').groupby(level=0)[0].apply('-'.join)
Out[42]:
0 024-764-108
1 002-231-531
2 005-231-329
3 012-345-678-901-234
Name: 0, dtype: object
If using python 3.6 you could consider 'f strings', f strings allow you to do some processing within the string.
f'{string[:3]}-{string[3:6]}-{string[6:]}'
Another option would be to split your string into three parts then do a join on the array.
split_string = [string[i: i + 3] for i in range(0, len(string), 3)]
formated_number = '-'.join(split_string)
The first line of this creates an array with sub strings of length 3, then it joins the elements of that array with a '-' character in between.
There is probably a better way to do this but you can use [] to split the string into sections of 3.
old_str = "024764108"
new_str = old_str[:3] + '-' + old_str[3:6] + '-' + old_str[6:]
Easy solution:
number = "024764108"
new_number = number[:3] + '-' + number[3:6]+ '-' + number[6:]
Consider this code, using string slicing: The segment of code that converts this str to your format is string[0:3] + "-" + string[3:6] + "-" + string[6:9]
Here is your updated method and some test cases. Also, it only accepts outputs which contain exactly 9 digits.
def format_digitstring(string:str):
if len(string) != 9:
return None
return string[0:3] + "-" + string[3:6] + "-" + string[6:9]
s1 = "024764108"
s2 = "002231531"
s3 = "005231329"
s4 = "00112341"
print(format_digitstring(s1))
print(format_digitstring(s2))
print(format_digitstring(s3))
print(format_digitstring(s4))
Output:
024-764-108
002-231-531
005-231-329
None
This also do:
import re
s='024764108'
print(('{}-'*2+'{}').format(*re.findall('(...)',s)))
or if you want to do it on all row, you can use panda's apply function.
Look ahead positive, \d{3} means three digits which followed with digit (?=\d), '-' is added after three digits ('\1-').
import re
number="024764108"
re.sub(r'(\d{3})(?=\d)',r'\1-',number)
I'm looking for an expression that will return $55.66 from this $ 55 66
Note: the amount of spaces between the $ and number could vary.
It will also need to work if the value is less than $10 i.e, something like $ 6 05
For the simple case you've described, you could just split and concatenate the string.
s = '$ 55 66'
s = s.split()
print s[0] + s[1] + '.' + s[2]
>>> $55.66
To support commas
s = '$ 424 552 66'
s = s.split()
print s[0] + ','.join(s[1:-1]) + '.' + s[-1]
>>> $424,552.66
This is the regular expression to find the dollar amount.
re.findall(r'(\$?\d+[,\.]\d+);
For example, if I have:
"+----+----+---+---+--+"
is it possible to replace from second to fourth + to -?
If I have
"+----+----+---+---+--+"
and I want to have
"+-----------------+--+"
I have to replace from 2-nd to 4-th + to -. Is it possible to achieve this by regex? and how?
If you can assume the first character is always a +:
string = '+' + re.sub(r'\+', r'-', string[1:], count=3)
Lop off the first character of your string and sub() the first three + characters, then add the initial + back on.
If you can't assume the first + is the first character of the string, find it first:
prefix = string.index('+') + 1
string = string[:prefix] + re.sub(r'\+', r'-', string[prefix:], count=3)
I would rather iterate over the string, and then replace the pluses according to what I found.
secondIndex = 0
fourthIndex = 0
count = 0
for i, c in enumerate(string):
if c == '+':
count += 1
if count == 2 and secondIndex == 0:
secondIndex = i
elif count == 4 and fourthIndex == 0:
fourthIndex = i
string = string[:secondIndex] + '-'*(fourthIndex-secondIndex+1) + string[fourthIndex+1:]
Test:
+----+----+---+---+--+
+-----------------+--+
I split the string into an array of strings using the character to replace as the separator.
Then rejoin the array, in sections, using the required separators.
example_str="+----+----+---+---+--+"
swap_char="+"
repl_char='-'
ith_match=2
jth_match=4
list_of_strings = example_str.split(swap_char)
new_string = ( swap_char.join(list_of_strings[0:ith_match]) + repl_char +
repl_char.join(list_of_strings[ith_match:jth_match]) +
swap_char + swap_char.join(list_of_strings[jth_match:]) )
print (example_str)
print (new_string)
running it gives :
$ python ./python_example.py
+----+----+---+---+--+
+-------------+---+--+
with regex? Yes, that's possible.
^(\+-+){1}((?:\+[^+]+){3})
explanation:
^
(\+-+){1} # read + and some -'s until 2nd +
( # group 2 start
(?:\+[^+]+){3} # read +, followed by non-plus'es, in total 3 times
) # group 2 end
testing:
$ cat test.py
import re
pattern = r"^(\+-+){1}((?:\+[^+]+){3})"
tests = ["+----+----+---+---+--+"]
for test in tests:
m = re.search(pattern, test)
if m:
print (test[0:m.start(2)] +
"-" * (m.end(2) - m.start(2)) +
test[m.end(2):])
Adjusting is simple:
^(\+-+){1}((?:\+[^+]+){3})
^ ^
the '1' indicates that you're reading up to the 2nd '+'
the '3' indicates that you're reading up to the 4th '+'
these are the only 2 changes you need to make, the group number stays the same.
Run it:
$ python test.py
+-----------------+--+
This is pythonic.
import re
s = "+----+----+---+---+--+"
idx = [ i.start() for i in re.finditer('\+', s) ][1:-2]
''.join([ j if i not in idx else '-' for i,j in enumerate(s) ])
However, if your string is constant and want it simple
print (s)
print ('+' + re.sub('\+---', '----', s)[1:])
Output:
+----+----+---+---+--+
+-----------------+--+
Using only comprehension lists:
s1="+----+----+---+---+--+"
indexes = [i for i,x in enumerate(s1) if x=='+'][1:4]
s2 = ''.join([e if i not in indexes else '-' for i,e in enumerate(s1)])
print(s2)
+-----------------+--+
I saw you already found a solution but I do not like regex so much, so maybe this will help another! :-)
I have a int 123. I need to convert it to a string "100 + 20 + 3"
How can I achieve it using Python?
I am trying to divide the number first (with 100) and then multiple the quotient again with 100. This seems to be pretty inefficient. Is there another way which I can use?
a = 123
quot = 123//100
a1 = quot*100
I am repeating the above process for all the digits.
Another option would be to do it by the index of the digit:
def int_str(i):
digits = len(str(i))
result = []
for digit in range(digits):
result.append(str(i)[digit] + '0' * (digits - digit - 1))
print ' + '.join(result)
which gives:
>>> int_str(123)
100 + 20 + 3
This works by taking each digit and adding a number of zeroes equal to how many digits are after the current digit. (at index 0, and a length of 3, you have 3 - 0 - 1 remaining digits, so the first digit should have 2 zeroes after it.)
When the loop is done, I have a list ["100", "20", "3"] which I then use join to add the connecting " + "s.
(Ab)using list comprehension:
>>> num = 123
>>> ' + '.join([x + '0' * (len(str(num)) - i - 1) for i, x in enumerate(str(num))])
'100 + 20 + 3'
How it works:
iteration 0
Digit at index 0: '1'
+ ('0' * (num_digits - 1 - iter_count) = 2) = '100'
iteration 1
Digit at index 1: '2'
+ ('0' * 1) = '20'
iteration 2
Digit at index 2: '3'
+
('0' * 0) = '3'
Once you've created all the "numbers" and put them in the list, call join and combine them with the string predicate +.
Another way of achieving what you intended to do:
def pretty_print(a):
aa = str(a)
base = len(aa) - 1
for v in aa:
yield v + '0' * base
base -= 1
>>> ' + '.join(pretty_print(123))
'100 + 20 + 3'
Here's my approach:
numInput= 123
strNums= str(numInput)
numberList= []
for i in range(0,len(strNums)):
digit= (10**i)*int(strNums[-(i+1)])
numberList.append(str(digit))
final= "+".join(numberList)
print(final)
It's the mathematical approach for what you want.
In number system every digit can be denoted as the 10 to the power of the actual place plus number(counting from zero from right to left)
So we took a number and converted into a string. Then in a loop we decided the range of the iteration which is equal to the length of our number.
range: 0 to length of number
and we give that number of power to the 10, so we would get:
10^0, 10^1, 10^2...
Now we need this value to multiply with the digits right to left. So we used negative index. Then we appended the string value of the digit to an empty list because we need the result in a form as you said.
Hope it will be helpful to you.
I recently coded a python solution using dictoionaries which got TLE verdict. The solution is exactly similar to a multiset solution in c++ which works. So, we are sure that the logic is correct, but the implementation is not upto the mark.
The problem description for understanding below code (http://codeforces.com/contest/714/problem/C):
For each number we need to get a string of 0s and 1s such that i'th digit is 0/1 if respective ith digit in number is even/odd.
We need to maintain the count of number that have the same mapping that is given by above described point.
Any hints/pointer to improve the performance of below code? It gave TLE (Time Limit Exceeded) for a large test case(http://codeforces.com/contest/714/submission/20594344).
from collections import defaultdict
def getPattern(s):
return ''.join(list(s.zfill(19)))
def getSPattern(s):
news = s.zfill(19)
patlist = [ '0' if (int(news[i])%2 == 0) else '1' for i in range(19) ]
return "".join(patlist)
t = int(raw_input())
pat = defaultdict(str) # holds strings as keys and int as value
for i in range(0, t):
oper, num = raw_input().strip().split(' ')
if oper == '+' :
pattern = getSPattern(str(num))
if pattern in pat:
pat[pattern] += 1
else:
pat[pattern] = 1
elif oper == '-' :
pattern = getSPattern(str(num))
pat[pattern] = max( pat[pattern] - 1, 0)
elif oper == '?' :
print pat.get(getPattern(num) , 0 )
I see lots of small problems with your code but can't say if they add up to significant performance issues:
You've set up, and used, your defaultdict() incorrectly:
pat = defaultdict(str)
...
if pattern in pat:
pat[pattern] += 1
else:
pat[pattern] = 1
The argument to the defaultdict() constructor should be the type of the values, not the keys. Once you've set up your defaultdict properly, you can simply do:
pat = defaultdict(int)
...
pat[pattern] += 1
As the value will now default to zero if the pattern isn't there already.
Since the specification says:
- ai — delete a single occurrence of non-negative integer ai from the multiset. It's guaranteed, that there is at least one ai in the
multiset.
Then this:
pat[pattern] = max( pat[pattern] - 1, 0)
can simply be this:
pat[pattern] -= 1
You're working with 19 character strings but since the specification says the numbers will be less than 10 ** 18, you can work with 18 character strings instead.
getSPattern() does a zfill() and then processes the string, it should do it in the reverse order, process the string and then zfill() it, as there's no need to run the logic on the leading zeros.
We don't need the overhead of int() to convert the characters to numbers:
(int(news[i])%2 == 0)
Consider using ord() instead as the ASCII values of the digits have the same parity as the digits themselves: ord('4') -> 52
And you don't need to loop over the indexes, you can simply loop over the characters.
Below is my rework of your code with the above changes, see if it still works (!) and gains you any performance:
from collections import defaultdict
def getPattern(string):
return string.zfill(18)
def getSPattern(string):
# pattern_list = (('0', '1')[ord(character) % 2] for character in string)
pattern_list = ('0' if ord(character) % 2 == 0 else '1' for character in string)
return ("".join(pattern_list)).zfill(18)
patterns = defaultdict(int) # holds keys as strings as and values as int
text = int(raw_input())
for _ in range(text):
operation, number = raw_input().strip().split()
if operation == '+':
pattern = getSPattern(number)
patterns[pattern] += 1
elif operation == '-':
pattern = getSPattern(number)
patterns[pattern] -= 1
elif operation == '?':
print patterns.get(getPattern(number), 0)
With the explanation already done by #cdlane, I just need to add my rewrite of getSPattern where I think the bulk of time is spent. As per my initial comment this is available on https://eval.in/641639
def getSPattern(s):
patlist = ['0' if c in ['0', '2', '4', '6', '8'] else '1' for c in s]
return "".join(patlist).zfill(19)
Using zfill(18) might marginally spare you some time.