TL;DR why is Python's eval function slow?
Hi. I was solving a coding exercise and noticed that the eval function was causing timeouts for some test cases and was wondering why this is the case as I don't particularly recall reading that the function is slow.
Here's the exercise:
You are given a list of n nonnegative integers and a target integer. Find out how many possible ways there are to add '+' or '-' in between the provided integers in order to obtain the target. For example, the input [1, 1, 1, 1, 1] and 3 would return 5 since there are five total ways to insert + or - to obtain 3: -1+1+1+1+1, +1-1+1+1+1, +1+1-1+1+1, +1+1+1-1+1, +1+1+1+1-1.
The solution I initially came up with used eval as follows:
from itertools import product
from typing import List
def solution(numbers: List[int], target: int) -> int:
numbers = [str(x) for x in numbers]
length = len(numbers)
all_operations = list(map(''.join, product('+-', repeat=length)))
answer = 0
for operation in all_operations:
eval_string = ''.join([''.join(x) for x in zip(operation, numbers)])
if eval(eval_string) == target:
answer += 1
return answer
The second solution that passes all test cases doesn't use eval and simply performs the arithmetic step by step:
from itertools import product
from typing import List
def solution(numbers: List[int], target: int) -> int:
numbers = [str(x) for x in numbers]
length = len(numbers)
all_signs = list(map(''.join, product('+-', repeat=length)))
answer = 0
for operation in all_signs:
value = 0
for op, number in zip(operation, numbers):
if op == '+':
value += int(number)
elif op == '-':
value -= int(number)
if value == target:
answer += 1
return answer
Converting your values to strings then running eval is not going to be efficient. This will be faster although it may be possible to improve this even further:
import itertools
INTS = [1, 1, 1, 1, 1]
CONST = 3
# build a new list comprised of the original INTS plus their negative values
P = list(map(lambda x: -x, INTS)) + INTS
# work out the unique permutations
s = set(itertools.permutations(P, len(INTS)))
# iterate over (map) each unique permutations to see if its sum matches CONST
print(list(map(lambda x: sum(x) == CONST, s)).count(True))
Related
This is a fairly straight forward programming problem in Python and I am looking for suggestions for further optimization. I am successfully processing in time except for very large strings. I am not looking for code rather areas that I should research for optimization improvements. I have already identified that I can skip even numbers reducing the loop operation and given the nature of the operations the pattern eventually repeats which is why I track when repeat occurs. This allows me break out if n > repeat. I am not positive if converting the string to a list is the most effective.
Problem:
We have a string s and we have a number n that indicates the number of times to run the function. Here is a function that takes your string, concatenates the even-indexed chars to the front, odd-indexed chars to the back. You perform this operation n times.
Example:
example where s = "qwertyuio" and n = 2:
after 1 iteration s = "qetuowryi"
after 2 iterations s = "qtorieuwy"
return "qtorieuwy"
def jumbled_string(s, n):
sl = list(s)
repeat = 0
for y in range(0,n):
for i in range(1, (len(sl)//2)+1):
sl.append(sl.pop(i))
if repeat == 0 and ''.join(sl) == s:
repeat = y+1
break
if repeat != 0:
afterrepeat = n%repeat
for y in range(0,afterrepeat):
for i in range(1, (len(sl)//2)+1):
sl.append(sl.pop(i))
return ''.join(sl)
I don't know what you mean by "pattern repeats". But if we stick to the problem statement, it's a one liner in Python:
s='abecidofug'
from itertools import chain
s2 = ''.join(chain([s[c] for c in range(0, len(s), 2)],[s[c] for c in range(1, len(s), 2)]))
s2
'aeioubcdfg'
In python 3.8+ (due to := operator) you could do it like this:
import collections
def jumbled_string(s: str, n: int) -> str:
generator = (s:=s[::2]+s[1::2] for _ in range(n))
collections.deque(generator, maxlen=0)
return s
Using collections.deque as this is the Fastest (most Pythonic) way to consume an iterator.
Though, for small n I'm finding it faster to use:
def jumbled_string(s: str, n: int) -> str:
for _ in (s:=s[::2]+s[1::2] for _ in range(n)):
pass
return s
Test:
jumbled_string("qwertyuio", 2)
Output:
'qtorieuwy'
You don't explain what n does. The statement is this:
def jumbled_string(s: str) -> str:
even = s[::2]
odd = s[1::2]
return even+odd
print(jumbled_string("0123456789"))
>>>0246813579
I was trying an online test. the test asked to write a function that given a list of up to 100000 integers whose range is 1 to 100000, would find the first missing integer.
for example, if the list is [1,4,5,2] the output should be 3.
I iterated over the list as follow
def find_missing(num)
for i in range(1, 100001):
if i not in num:
return i
the feedback I receives is the code is not efficient in handling big lists.
I am quite new and I couldnot find an answer, how can I iterate more efficiently?
The first improvement would be to make yours linear by using a set for the repeated membership test:
def find_missing(nums)
s = set(nums)
for i in range(1, 100001):
if i not in s:
return i
Given how C-optimized python sorting is, you could also do sth like:
def find_missing(nums)
s = sorted(set(nums))
return next(i for i, n in enumerate(s, 1) if i != n)
But both of these are fairly space inefficient as they create a new collection. You can avoid that with an in-place sort:
from itertools import groupby
def find_missing(nums):
nums.sort() # in-place
return next(i for i, (k, _) in enumerate(groupby(nums), 1) if i != k)
For any range of numbers, the sum is given by Gauss's formula:
# sum of all numbers up to and including nums[-1] minus
# sum of all numbers up to but not including nums[-1]
expected = nums[-1] * (nums[-1] + 1) // 2 - nums[0] * (nums[0] - 1) // 2
If a number is missing, the actual sum will be
actual = sum(nums)
The difference is the missing number:
result = expected - actual
This compulation is O(n), which is as efficient as you can get. expected is an O(1) computation, while actual has to actually add up the elements.
A somewhat slower but similar complexity approach would be to step along the sequence in lockstep with either a range or itertools.count:
for a, e in zip(nums, range(nums[0], len(nums) + nums[0])):
if a != e:
return e # or break if not in a function
Notice the difference between a single comparison a != e, vs a linear containment check like e in nums, which has to iterate on average through half of nums to get the answer.
You can use Counter to count every occurrence of your list. The minimum number with occurrence 0 will be your output. For example:
from collections import Counter
def find_missing():
count = Counter(your_list)
keys = count.keys() #list of every element in increasing order
main_list = list(range(1:100000)) #the list of values from 1 to 100k
missing_numbers = list(set(main_list) - set(keys))
your_output = min(missing_numbers)
return your_output
Given an array of integers, process each array element and return the resultant array.
If the element is an even no., add 2 to it.
If the element is an odd no., add 1 to it.
The array will not contain any negative integers.
for eg.,
Input 1 : {1,2,3,4,5}
Input 2 : 5 (no. of elements in input 1 array)
Output : {2,4,4,6,6}
My code :
def EvenOddSum(a, n):
even = 0
odd = 0
for i in range(n):
if i % 2 == 0:
even += a[i]
else:
odd += a[i]
print("Even index positions sum ", even )
print ("Odd index positions sum ", odd )
arr=input("Enter array : ")
n = int(input("Enter number of elements in arr : "))
print(EvenOddSum(arr, n) )
I tried this but this . But code is showing unsupported operand type error. Plus I can't achieve the logic stated in problem.
How to achieve the solution for my problem . Please help.
This line:
arr=input("Enter array : ")
gives you a str, not a list. You might want something more like:
arr = list(map(int, input("Enter array : ").split()))
which takes the str input, splits it into a List[str], and then maps it to a List[int].
Note that this assumes you're entering the array as "1 2 3 4", not some tricky syntax like "{1,2,3,4}" (which you could write more code to handle, but if it's not part of the assignment I wouldn't recommend deliberately making your life more difficult).
You should also define your function to just take the array, rather than the array plus the count (since the array already contains the length). Then to test it you can simply get the array from stdin and invoke the function all in one step:
>>> def even_odd_sum(a):
... return [i + (2 - i % 2) for i in a]
...
>>> even_odd_sum(list(map(int, input("Enter array: ").split())))
Enter array: 1 2 3 4 5
[2, 4, 4, 6, 6]
If you wanted to exactly match the funky formatting given in the original question, that's significantly more work than implementing the actual function, but it might look like:
from typing import Any, List
def even_odd_sum(a: List[int]) -> List[int]:
return [i + (2 - i % 2) for i in a]
def input_funky_formatted_list(funk: str) -> List[str]:
if funk[0] != "{" or funk[-1] != "}":
raise ValueError("this formatting is insufficiently funky")
return funk[1:-1].split(",")
def output_funky_formatted_list(arr: List[Any]) -> str:
return "{" + ",".join(map(str, arr)) + "}"
arr = list(map(int, input_funky_formatted_list(input("Input 1 : "))))
input("Input 2 : ") # discard this, it serves no purpose
print(f"Output : {output_funky_formatted_list(even_odd_sum(arr))}")
The first problem of the code is that the result of input() is a string independent of what you actually type.
To get arr to eventually contain an array of numbers (possibly into a list), you should parse the string resulting from input() or adopt another input strategy (see below on third issue).
Python libraries are incredibly helpful with this, and you could consider using ast.literal_eval() for the job, e.g.:
import ast
s = '{1,2,3,4,5}'
items = ast.literal_eval('[' + s[1:-1] + ']')
print(items)
# [1, 2, 3, 4, 5]
If you have some freedom over the input, you could spare yourself some work or even take a more direct approach.
The second problem is that EvenOddSum() (which incidentally is not named according to typical Python coding style conventions) is not implementing the logic to fulfill your expectations.
One way would be:
# offsets=(even_offset, odd_offset)
def odd_even_sum(items, offsets=(2, 1)):
return [item + offsets[item % 2] for item in items]
The third issue is that if you input a string containing all the array, the information about its length is redundant, unless you make some use of it.
For example, you could use it to check if the array is of the desired length:
assert(len(items) == n)
or use it to simplify your input:
def odd_even_sum(items, offsets=(2, 1)):
return [item + offsets[item % 2] for item in items]
n = int(input('Array length?'))
items = []
for i in range(n):
item = int(input(f'Item[{i}]?'))
items.append(item)
Putting all this together:
def odd_even_sum(items, offsets=(2, 1)):
return [item + offsets[item % 2] for item in items]
n = int(input('Array length?'))
items = []
for i in range(n):
item = int(input(f'Item[{i}]?'))
items.append(item)
new_items = odd_even_sum(items)
print(f' Input: {items}')
print(f'Output: {new_items}')
I am trying to find the complement of a number in Python. I know there are other solutions but I am trying to make my own.
My first try:
def findComplement(self, num):
"""
:type num: int
:rtype: int
"""
numb = str(num)
i = 0
while(i<=len(numb)):
if numb[i] == "0":
numb[i] = "1"
else:
numb[i] = "0"
i=i+1
return int(numb)
But strings are immutable so it gave an error,
My second try:
def findComplement(self, num):
"""
:type num: int
:rtype: int
"""
numb = str(num)
numb2 = []
k =0
for j in numb:
numb2[k] = j #error on this line
k=k+1
i = 0
while(i<=len(numb2)):
if numb2[i] == "0":
numb2[i] = "1"
else:
numb2[i] = "0"
i=i+1
return int(numb2)
Error in program 2:
Line 11: IndexError: list assignment index out of range
Since other answers cover your main problem, you can also just use a dictionary for mapping 1 to 0, and vice versa:
>>> d = {'0': '1', '1': '0'}
>>> s = '0101'
>>> ''.join(d[x] for x in s)
'1010'
So I wanted find the binary complement of a number myself
but was unstatisfied with how the mask is generated and other things, cause they involved using the string representation of the binray number.
So I dug a little deeper and came across this reddit post
where they generated the mask via bitwise shifts.
And I also found out that you can get the bit count of an integer
with int.bit_lenght(<your_number>),
instead of using len(bin(<your-number>)[2:]).
Only problem is: that it returns 0 for the number 0.
So you have to account for that.
Tho I have no clue what method is more performant
and whether it is worth it compared to string manipulation.
But in the end that was my final solution
that I found was pretty neat cause it doesn't involve strings.
def bin_complement(num, on_two=False, bit_count=0):
"""
num(int) dezimal number
on_two(bool) complement on 2 else on 1
bit_count = number of bits for the mask, defaults to bits in num
returns(int) complementary number
"""
bit_count = max(bit_count, num.bit_length(), 1)
mask = 2 ** bit_count - 1
complement = num ^ mask
if on_two:
complement += 1
return complement
Edit: 2**bit_count - 1 is a faster way for doing
for _ in range(bit_count):
mask <<= 1
mask |= 1
Edit2: If you want to take the complement of fixed length bit number.
E.G: C_1(0001) -> 0001 xor 1111 = 1110
and not just 0000.
You can now set a bit_count value for the mask
so the complement works correct.
You have a few problems.
First you are not converting your input to a binary string properly. This can be done by numb = bin(num)[2:] instead of num = str(num).
Second, you are trying to index into your empty numb2 list. Actually there is no need to create a separate numb2 list, you can just operate directly on your numb string, as in your first attempt. For example:
for i in range(len(numb)):
if numb[i] == "0":
numb[i] = "1"
else:
numb[i] = "0"
Finally, to convert your binary string back to an int you should do int(numb, 2) not int(numb).
numb2 is an empty list. It does not have any elements to be addressed (and updated). What you need is a bytearray, rather than a list (or a string):
def findComplement(self, num):
"""
:type num: int
:rtype: int
"""
numb = bytearray(str(num), 'utf-8')
i = 0
while(i<len(numb)):
if numb[i] == ord("0"):
numb[i] = ord("1")
else:
numb[i] = ord("0")
i=i+1
return int(numb)
As can be seen, this requires some minor other changes, as the stored data in a bytearray is a byte (0-255) rather than a char. The solution is to use the ord function, to get the byte number from "0" and "1". In addition there is a small issue (off by one) in your whileexpression. Since the indexing and your count start at zero i should be strictly smaller than the length of the string.
I would also like to add that there are simper ways to achive the complement of 0 and/or 1, though.
Let's say we have to find complement of 101 if we xor the input with the mask 111 then we will get 010 which is complement.
def findComplement(self, num: int) -> int:
mask_len=len(bin(num))-2
mask=int("1"*mask_len,2)
return mask^num
For those looking for a one-liner, here is one:
int(bin(value)[2:].zfill(numbits).translate(str.maketrans('01', '10')), 2)
The above computes a numbits-bit ones' complement for value.
Since str.maketrans('01', '10') is a constant (dictionary), one can compute it ahead of time:
dMatrix = str.maketrans('01', '10') # which is {48: 49, 49: 48}
int(bin(value)[2:].zfill(numbits).translate(dMatrix), 2)
Converting this one-liner to a function is straightforward:
def onescomplement(value, numbits):
return int(bin(value)[2:].zfill(numbits).translate(str.maketrans('01', '10')), 2)
HTH
Don't you just mean the ~ operator, e.g:
def findComplement(self, num):
return ~num
I found out that in python, if you use bin:
>>> bin(4)
'0b100'
>>> bin(-4)
'-0b100'
but if you try this:
-4 >> 8 => -1
in fact, when right shift more than 3 bits, the answer is always -1. I know this is implementation stuff, but I came across this problem:
In the following array, find out the number which appears only once, while others appears always three times
[-2,-2,1,1,-3,1,-3,-3,-4,-2]
This is the code
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if nums == None:
return None
ans = 0
for i in range(32):
numOfI = 0
for num in nums:
if (( num >> i) & 1) == 1 :
numOfI += 1
numOfI %=3
print i,numOfI
if numOfI != 0:
ans = ans + (numOfI << i)
return ans
the answer should be -4, but instead I get 11111111111111111111111111111100; how can I convert it into -4?
Bit manipulation on Python are easily done with int.from_bytes and int.to_bytes.
On the other hand, solving the "find the value appearing x times" is easily solved using the Counter class:
>>> from collections import Counter
>>> values = [-2,-2,1,1,-3,1,-3,-3,-4,-2]
>>> [value for value, count in Counter(values).items() if count == 1]
[-4]
Using from_bytes and to_bytes is clear and readable, so you don't want to try to do the conversion yourself.
Using Counter is also more readable than your cryptic implementation, yet my one-liner may not be the most readable imaginable version, it's still a single line and far more readeable.