Is there any way I could optimize this number search with multiprocessing? - python

from perms import perms
def dfac(n):
potential_factors = [9, 8, 7, 6, 4, 3, 2, 1]
factors = [9, 8]
n //= 72
while n != 1:
for f in potential_factors:
if n % f == 0 and f != 1:
factors.append(f)
n //= f
break
elif f != 1:
potential_factors = potential_factors[1:]
break
else:
return ['f']
return factors[::-1]
##############################################################################
version = 1
perms_length = len(perms)
index = 17701
def search(start: int):
for ones in range(start, index + 9144 * version, 9): # ignore this weird range
i = 1
for p in perms:
if len(dfac(int('1' * ones + p))) == 1:
print(f'{ones} ({i} / {perms_length})')
else:
cool = f'\n\n\nOnes: {ones}\nPerm: {p}\n\n\n'
print(cool)
exit()
i += 1
search(20077)
perms is a list of 113600 permutations of a number (as strings). I'm appending a bunch of ones to the beginning of each permutation and trying to factor each into single-digit factors (dfac). dfac returns fail string in list ['f'] if it fails to factor the number into any combination of [9, 8, 7, 6, 4, 3, 2]. How could I split this up with multiprocessing to make this go faster?

Related

Find The Parity Outlier, CodeWars question on python

I am trying to solve this problem but it doesn't give me the correct answer. Here is the problem:
You are given an array (which will have a length of at least 3, but could be very large) containing integers. The array is either entirely comprised of odd integers or entirely comprised of even integers except for a single integer N. Write a method that takes the array as an argument and returns this "outlier" N.
Here is my code:
a = [2, 4, 6, 8, 10, 3]
b = [2, 4, 0, 100, 4, 11, 2602, 36]
c = [160, 3, 1719, 19, 11, 13, -21]
def find_outlier(list_integers):
for num in list_integers:
if num % 2 != 0:
odd = num
elif num % 2 == 0:
even = num
for num in list_integers:
if len([odd]) < len([even]):
return odd
else:
return even
print(find_outlier(a))
print(find_outlier(b))
print(find_outlier(c))
It spits out 10, 36, 160 and obviously only the last one is correct. Can anyone help me out with it?
Thanks!
You could analyze the first three and find the outlier if it is there.
If it is there, you are done. If not, you know the expected parity and can test each subsequent element accordingly.
Creating lists for odd/even numbers, while in principle leading to a result, is unnecessarily memory inefficient.
In code this could look something like:
def find_outlier(seq):
par0 = seq[0] % 2
par1 = seq[1] % 2
if par0 != par1:
return seq[1] if seq[2] % 2 == par0 else seq[0]
# the checks on the first 2 elements are redundant, but avoids copying
# for x in seq[2:]: would do less iteration but will copy the input
for x in seq:
if x % 2 != par0:
return x
a = [2, 4, 6, 8, 10, 3]
b = [2, 4, 0, 100, 4, 11, 2602, 36]
c = [160, 3, 1719, 19, 11, 13, -21]
print(find_outlier(a))
# 3
print(find_outlier(b))
# 11
print(find_outlier(c))
# 160
Your code could not work in its current form:
this block:
for num in list_integers:
if num % 2 != 0:
odd = num
elif num % 2 == 0:
even = num
will just have the last seen odd in odd and the last seen even in even, without any info on how many were seen. You would need to count how many even/odd numbers are there and eventually need to store the first value encountered for each parity.
this second block
for num in list_integers:
if len([odd]) < len([even]):
return odd
else:
return even
is always checking the length of the length-1 lists, and will always return even.
I see no simple way of fixing your code to make it with comparable efficiency as the above solution. But you could adapt your code to make it reasonably efficient (O(n) in time -- but without short-circuiting, O(1) in memory):
def find_outlier_2(seq):
odd = None
even = None
n_odd = n_even = 0
for x in seq:
if x % 2 == 0:
if even is None: # save first occurrence
even = x
n_even += 1
else: # no need to compute the modulus again
if odd is None: # save first occurrente
odd = x
n_odd += 1
if n_even > 1:
return odd
else:
return even
The above is significantly more efficient than some of the other answers in that it does not create unnecessary lists.
For example, these solutions are unnecessarily more memory consuming (being O(n) in time and O(n) in memory):
def find_outlier_3(list_integers):
odd = []
even = []
for num in list_integers:
if num % 2 != 0:
odd.append(num)
elif num % 2 == 0:
even.append(num)
if len(odd) < len(even):
return odd[0]
else:
return even[0]
def find_outlier_4(lst):
odds = [el % 2 for el in lst]
if odds.count(0) == 1:
return lst[odds.index(0)]
else:
return lst[odds.index(1)]
Simple benchmarks show that these solutions are also slower:
%timeit [find_outlier(x) for x in (a, b, c) * 100]
# 10000 loops, best of 3: 128 µs per loop
%timeit [find_outlier_2(x) for x in (a, b, c) * 100]
# 1000 loops, best of 3: 229 µs per loop
%timeit [find_outlier_3(x) for x in (a, b, c) * 100]
# 1000 loops, best of 3: 341 µs per loop
%timeit [find_outlier_4(x) for x in (a, b, c) * 100]
# 1000 loops, best of 3: 248 µs per loop
You can nicely use list comprehensions for this:
a = [2, 4, 6, 8, 10, 3]
b = [2, 4, 0, 100, 4, 11, 2602, 36]
c = [160, 3, 1719, 19, 11, 13, -21]
def outlier(lst):
odds = [ el % 2 for el in lst ] # list with 1's when odd, 0's when even
print(odds) # just to show what odds contains
if odds.count(0) == 1: # if the amount of zeros (even numbers) = 1 in this list
print(lst[odds.index(0)]) # find the index of the 'zero' and use it to read the value from the input lst
else:
print(lst[odds.index(1)]) # find the index of the 'one' and use it to read the value from the input lst
outlier(a)
outlier(b)
outlier(c)
Output
[0, 0, 0, 0, 0, 1] # only 1 'one' so use the position of that 'one'
3
[0, 0, 0, 0, 0, 1, 0, 0] # only 1 'one' so use the position of that 'one'
11
[0, 1, 1, 1, 1, 1, 1] # only 1 'zero' so use the position of that 'zero'
160
Count the number of odd values in the first three items in the list. This can be done using a sum(). It the sum > 1, the list has mostly odd numbers, so find the even outlier. Otherwise find the odd outlier.
def find_outlier(sequence):
if sum(x & 1 for x in numbers[:3]) > 1:
# find the even outlier
for n in sequence:
if not n & 1:
return n
else:
# find the odd outlier
for n in sequence:
if n & 1:
return n
I imagine it would be a bit more efficient to first determine if the outlier is odd or even by looking at a small sample, then return just the outlier using list comprehension. This way, if the list is massive, you won't have timeout issues.
Here's what I would do:
def findoutlier(yourlist):
if (yourlist[0] % 2 == 0 and yourlist[1] % 2 == 0) or (yourlist[0] % 2 == 0 and yourlist[2] % 2 == 0) or (yourlist[1] % 2 == 0 and yourlist[2] % 2 == 0):
oddeven = "even"
else:
oddeven = "odd"
if oddeven == "even":
return [i for i in yourlist if i % 2 != 0][0]
else:
return [i for i in yourlist if i % 2 == 0][0]
a = [2, 4, 6, 8, 10, 3]
b = [2, 4, 0, 100, 4, 11, 2602, 36]
c = [160, 3, 1719, 19, 11, 13, -21]
print(findoutlier(a))
print(findoutlier(b))
print(findoutlier(c))
This will return 3, 11, and 160 as expected.
You want to use a list to store your odd/even numbers. Right now you're storing them as int and they're getting replaced on your loop's next iteration.
def find_outlier(list_integers):
odd = []
even = []
for num in list_integers:
if num % 2 != 0:
odd.append(num)
elif num % 2 == 0:
even.append(num)
if len(odd) < len(even):
return odd[0]
else:
return even[0]

Python sum of number in array, ignoring sections of specific numbers

I am very new to Python and have been going through multiple tutorials to get better.
I have straggled with one difficult problem and found a solution. But it feels, works very newbie like. I think that I have tailored it to answer the specific question.
So the question is:
SUMMER OF '69: Return the sum of the numbers in the array, except ignore sections of numbers starting with a 6 and extending to the next 9 (every 6 will be followed by at least one 9). Return 0 for no numbers.
summer_69([1, 3, 5]) --> 9
summer_69([4, 5, 6, 7, 8, 9]) --> 9
summer_69([2, 1, 6, 9, 11]) --> 14
My code to solve this is:
def summer_69(arr):
list1 = arr
summ = int()
for i in range(0, len(arr)):
if 6 in list1:
listOfRange = range(list1.index(6), list1.index(9) + 1)
for index in listOfRange:
print(listOfRange)
arr[index] = 0
if 6 != arr[i]:
summ += arr[i]
else:
continue
else:
summ += arr[i]
return summ
It is a very basic problem and I am very alerted that I have struggled with something like this already.
Short O(n) solution using an iterator and the in operator to search for (and thereby skip to) the 9 following each 6:
def summer_69(lst):
it = iter(lst)
return sum(x for x in it
if x != 6 or 9 not in it)
Less dense version:
def summer_69(lst):
it = iter(lst)
total = 0
for x in it:
if x == 6:
9 in it
else:
total += x
return total
Correctness check (random test cases) and benchmark (with [1] * 5000 + [6, 9] * 2500) along with the accepted answer's solution (which takes O(n2)):
30 out of 30 tests correct
303045 us 303714 us 304335 us 306007 us 309986 us summer_69_Accepted
444 us 446 us 464 us 478 us 527 us summer_69_Kelly1
442 us 448 us 453 us 465 us 500 us summer_69_Kelly2
Code (Try it online!):
from timeit import repeat
def summer_69_Accepted(lst):
copyoflist = lst[:] # makes shallow copy of list
while True:
if 6 not in copyoflist:
return sum(copyoflist)
indexof6 = copyoflist.index(6)
indexof9 = copyoflist.index(9, indexof6+1) # begin search for 9 after 6
del copyoflist[indexof6:indexof9+1]
def summer_69_Kelly1(lst):
it = iter(lst)
return sum(x for x in it
if x != 6 or 9 not in it)
def summer_69_Kelly2(lst):
it = iter(lst)
total = 0
for x in it:
if x == 6:
9 in it
else:
total += x
return total
funcs = summer_69_Accepted, summer_69_Kelly1, summer_69_Kelly2
from random import randrange, choices
def testcase():
def others():
return choices([0, 1, 2, 3, 4, 5, 7, 8], k=randrange(10))
lst = others()
for _ in range(10):
lst += [6, *others(), 9, *others()]
return lst
tests = correct = 0
for _ in range(10):
lst = testcase()
expect = funcs[0](lst.copy())
for func in funcs:
result = func(lst.copy())
correct += result == expect
tests += 1
print(correct, 'out of', tests, 'tests correct')
print()
lst = [1] * 5000 + [6, 9] * 2500
for func in funcs:
times = repeat(lambda: func(lst), number=1)
print(*('%6d us ' % (t * 1e6) for t in sorted(times)), func.__name__)
Here's how I'd do it, as a first cut:
def summer_69(series):
in_summer = False
cur_sum = 0
for v in series:
if in_summer:
if v == 9:
in_summer = False
else:
if v == 6:
in_summer = True
else:
cur_sum += v
return cur_sum
Here's a version that uses a more reusable pythonic idiom, a generator function, and is a little more compact (at the slight cost of an extra comparison):
def yield_non_summer(series):
in_summer = False
def stateful_summer_predicate(v):
nonlocal in_summer
if in_summer and v == 9:
in_summer = False
return True # 9 is still in summer
elif not in_summer and v == 6:
in_summer = True
return in_summer
return (v for v in series if not stateful_summer_predicate(v))
def summer_69(series):
return sum(yield_non_summer(series))
Or, in fewer lines:
def yield_non_summer(series):
in_summer = False
def stateful_summer_predicate(v):
nonlocal in_summer
in_summer = (in_summer or v == 6) and v != 9
return in_summer
return (v for v in series if not stateful_summer_predicate(v))
def summer_69(series):
return sum(yield_non_summer(series))
def summer_69(arr):
sum = 0
Flag = False
if 6 not in arr:
for num in arr:
sum = sum + num
return sum
else:
for num in arr:
if num != 6 and Flag == False:
sum = sum + num
elif num == 6:
Flag = True
continue
elif Flag == True and num != 9:
continue
elif num == 9:
Flag = False
return sum
A simple approach is to make a filter and sum the results.
Code
def filter_substring(seq, start, end):
"""Yield values outside a given substring."""
release = True
for x in seq:
if x == start:
release = False
elif x == end:
release = True
elif release:
yield x
def summer(seq):
"""Return the sum of certain values."""
return sum(filter_substring(seq, 6, 9))
Demo
assert 0 == summer([])
assert 6 == summer([1, 2, 3])
assert 6 == summer([1, 2, 3, 6, 8, 7, 9])
assert 9 == summer([1, 3, 5])
assert 8 == summer([3, 5, 6, 7, 8, 9])
assert 15 == summer([2, 1, 6, 9, 12])
assert 16 == summer([2, 1, 6, 9, 1, 6, 6, 120, 9, 9, 12])
Details
filter_substring()+
This is a generator function. It iterates the input sequence and only yields a value if the conditions are appropriate, i.e. when the release remains True.
>>> list(filter_substring("abcde", "c", "d"))
['a', 'b', 'e']
>>> list(filter_substring([0, 1, 2, 3, 10], 1, 3))
[0, 10]
summer()
Here we simply sum whatever filter_range() yields.
+Note: a substring is a contiguous subsequence; this may or may not include strings in Python.
Will work with indexes:
def summer_69(arr):
y = []
for x in arr:
if 6 in arr:
a = arr.index(6)
b = arr.index(9)
del arr[a:b+1]
y = arr
elif arr == []:
return "0"
else:
return sum(arr)
return sum(y)
print(summer_69([])) #0
print(summer_69([1, 3, 5])) #9
print(summer_69([4, 5, 6, 7, 8, 9])) #9
print(summer_69([2, 1, 6, 9, 11])) #14
print(summer_69([2, 1, 6, 9, 6, 11, 25, 36, 11, 9, 4, 6, 4, 6, 3, 9, 15])) #22
Something like this:
def summer_69(lst):
"""Return the sum of the numbers in the array,
except ignore sections of numbers starting with a 6 and extending to the next 9
(every 6 will be followed by at least one 9). Return 0 for no numbers
"""
if not lst:
return 0
else:
_sum = 0
active = True
for x in lst:
if active:
if x != 6:
_sum += x
else:
active = False
else:
if x == 9:
active = True
return _sum
print(summer_69([1, 3, 5]))
print(summer_69([4, 5, 6, 7, 8, 9]))
print(summer_69([2, 1, 6, 9, 11]))
output
9
9
14
def summer_69(arr):
if 6 in arr:
c=arr[arr.index(6):arr.index(9)+1]
for i in c:
arr.remove(i)
print(arr)
return sum(arr)
else:
return sum(arr)
summer_69([1,2,3,4,5,6,7,8,9,10,11,12])
This will work:
def summer_69(arr):
total = 0
add = True
for num in arr:
while add:
if num != 6:
total += num
break
else:
add = False
while not add:
if num != 9:
break
else:
add = True
break
return total
def summer_69(arr):
a = 0
for nums in arr:
if nums == 6:
for items in arr[arr.index(6):]:
a = a+ items
if items == 9:
break
return sum(arr)-a
def summer_69(arr):
x = arr.count(6)
y = arr.count(9)
# to decide number of iteration required for loop
z = min(x,y)
k = 0
while k < (z) :
m = arr.index(6)
n = arr.index(9)
del arr[m:(n+1)]
k = k + 1
print(arr)
return sum(arr)
This will work for summer_69 problem as well for filtering substring
def filter_substring(seq, start, end):
flag = False
for char in seq:
if char == start:
flag = True
continue
elif flag:
if char == end:
flag = False
else:
continue
else:
yield char
def summer_69(seq, start, end):
return sum(filter_substring(seq, start, end))
def print_substring(string, start, end):
return list(filter_substring(string, start, end))
Example ::
seq = [4, 5, 9, 6, 2, 9, 5, 6, 1, 9, 2]
print(summer_69(seq, start=6, end=9))
string = "abcdef"
print(print_substring(string, start='c', end='e'))
This is the probably best answer if you are a newbie. I have simplified it as much as i can. you only need to know enumerate, function, for loops , tuple unpacking,if/else statements and break function.So lets go straight to the answer.
def myfunc(a):
mylist=[]
sum1 = 0
for b,c in enumerate(a):
if c==6:
for d in a[:b]:
mylist.append(d)
for e,f in enumerate(a):
if f==9:
for j in a[e+1:]:
mylist.append(j)
for y in a:
if y==6:
break
else:
mylist.append(y)
for k in mylist:
sum1 = sum1+k
print(sum1)
myfunc([1,3,5])
For those interested, here is my solution for this problem:
def summer_69(arr):
skate = arr
guitar = []
for i in range(len(arr)):
if 6 in arr:
guitar = skate[skate.index(6):skate.index(9)+1]
return abs(sum(skate) - sum(guitar))
else:
return sum(skate)
Replace
list1.index(9)+1
by
list1.index(9,list1.index(6)+1)+1
in line 6.
This will start searching for a 9 after 6.
This is taken from a Udemy course.
Here is the official answer . . .
def summer_69(arr):
total = 0
add = True
for num in arr:
while add:
if num != 6:
total += num
break
else:
add = False
while not add:
if num != 9:
break
else:
add = True
break
return total
Jose Periera has this on Python 'zero to hero' course.
My own particular approach was this . . .
def summer_69(arr):
#first find out if 6 or 9 are in the list
if 6 in arr and 9 in arr:
#Then create a variable that stores the index location of the number 6
#and the number 9
sixer = arr.index(6)
niner = arr.index(9)
#now return the sum of the array minus the sum of the values between
#index of 6 and index of 9 inclusive (hence the plus 1)
#This way will ignore the case of a 9 appearring before a 6 too.
return sum(arr) - sum(arr[sixer:niner+1])
#Otherwise just return the sum of the array.
else:
return sum(arr)
Happy to accept criticism here. I'm learning Python myself and I'm undergoing an Msc in computer science and hoping to apply for jobs in the field soon, so your comments will help me :)
My approach was to sum the whole list and the part of the list that we want to ignore and subtract them at the end.
def summer_69(arr):
result=0
reduction =0
for i in range(0,len(arr)):
result+=arr[i]
if arr[i] == 6:
temp = arr[arr.index(6):arr.index(9)+1]
reduction = sum(temp)
return result - reduction
def summer69(a):
for z in a:
if z==6 and 9 in a:
x=a.index(6)
y=a.index(9)
del a[x:y+1]
t= sum(a)
else:
t=sum(a)
return t
Will always prefer short , clear and easy understandable solution.
def summer_69(arr):
if 9 in arr :
sum = 0
y = arr.index(9)
for i , l in enumerate(arr):
if l == 6:
del arr[i:y+1]
for i in range(len(arr)):
sum = sum + arr[i]
return sum
elif 9 not in arr:
sum = 0
for i in range(len(arr)):
sum = sum + arr[i]
return sum
def summer_69(mylist):
if 6 in mylist:
return sum(mylist) - sum(mylist[mylist.index(6):mylist.index(9)+1])
else:
return sum(mylist)
def summer_69(arr):
returner = []
if 6 and 9 in arr:
a = arr.index(6)
b = arr.index(9)
if a < b:
seq = arr[a:(b+1)]
for i in arr:
if i not in seq:
returner.append(i)
return (sum(returner))
elif a > b:
seq = arr[b:(a+1)]
for i in arr:
if i not in seq:
returner.append(i)
return (sum(returner))
elif 6 in arr:
a = arr.index(6)
seq = arr[a:]
for i in arr:
if i not in slicer:
returner.append(i)
return(sum(returner))
elif 9 in arr:
a = arr.index(9)
seq = arr[a:]
for i in arr:
if i not in slicer:
returner.append(i)
return(sum(returner))
elif arr == []:
return 0
else:
return (sum(arr))
Just learning Python too and this was what I came up with for it:
def myfunc(arr):
ignore_list = []
newlist = []
for i,v in enumerate(arr):
if v >= 6 and v <= 9:
ignore_list.append(i)
if i in ignore_list:
newlist.append(0)
else:
newlist.append(v)
return sum(newlist)

Reduce runtime when finding permutations

I am working on one of Google's FooBar challenges for fun and running into a Time Limit Exceeded error when executing it in Google's run time environment. My solution runs fine - and to my eyes quickly - in my local development environment.
This is my code:
from itertools import permutations
def answer(l):
options = []
for r in range(1, len(l) + 1):
for p in permutations(l, r=r):
n = int(''.join(map(str, p)))
if n % 3 == 0:
options.append(n)
numbers = sorted(options, reverse=True)
return numbers[0]
l = [3, 1, 4, 1, 5, 9]
#l = [3, 1, 4, 1]
print(answer(l))
The goal is to find the largest number that is divisible by 3 from the list of numbers that is passed in.
The output of the two examples should be:
[3, 1, 4, 1, 5, 9] => 94311
[3, 1, 4, 1] => 4311
Based on the comments to generate the permutations from largest to smallest (instead of smallest to largest) and then breakout, I've modified the code. Against, it works in the local environment but the Google runtime says that the timelimit has been exceeded:
def answer(l):
options = []
l = sorted(l, reverse=True)
for r in range(len(l) + 1, 1, -1):
for p in permutations(l, r=r):
n = int(''.join(map(str, p)))
if n % 3 == 0:
return n
return 0
I am sorting the input list based on the permutations docs that say the tuples will be sorted if the input is sorted. It then, since it should be sorted, the first time it finds a value divisible by 3, that will be the higest value
As I said, my code (both versions) works. But, I seem to be running longer than Google expects. How can I reduce the run time of the above code?
The highest number will have the most number of digits. So for a
list of size n, the search should start from n and continue to n-1,
n-2...
The numbers divisible by 3 will always be in the solution. For
example 2514 is divisible by 3 so is 32514 or 35314. Therefore you can reduce
the search to the digits that are not divisible by 3.
For a list of n digits that are not divisible by 3 (n>=3), you can
get a number divisible by 3 by removing at most 2 digits. This is because the summation will have the remainder 1 or 2. If it is 1, in the worst case you can remove 2 digits with a remainder of 2. If it is 2, again in the worst case you can remove 2 digits with a remainder of 1.
Now the algorithm:
You have a list of numbers:
divisible = [i for i in numbers if i % 3 == 0]
candidate_list = [i for i in numbers if i % 3 != 0]
If the summation of candidate_list is divisible by 3, then you have the answer. If not, look at the remainder:
remainder = sum(candidate_list) % 3
If the remainder is 1, we are going to search for 1, 4, or 7 in the candidate list. If it is 2, the numbers will be 2, 5, and 8. If we find a number, we are going to remove that from the list and the sum of the remaining digits will be divisible by three.
if remainder!=0:
for i in range(3):
if (remainder + i*3) in candidate_list:
candidate_list.remove(remainder + i*3)
return candidate_list
This will start the search from the smallest digit and break out of the loop when a digit is found. If not, we will search for two digits instead of 1.
counter = 0
for candidate in candidate_list:
if candidate % 3 + remainder == 3:
candidate_list.remove(candidate)
counter += 1
if counter > 1:
return candidate_list
Overall, you'll have something like this:
numbers = [3, 1, 4, 1, 5, 9, 0, 2, 4, 7, 9, 1, 3]
divisible = [i for i in numbers if i % 3 == 0]
def search(numbers):
candidate_list = sorted([i for i in numbers if i % 3 != 0])
remainder = sum(candidate_list) % 3
if remainder!=0:
for i in range(3):
if (remainder + i*3) in candidate_list:
candidate_list.remove(remainder + i*3)
return candidate_list
counter = 0
for candidate in candidate_list:
if candidate % 3 + remainder == 3:
candidate_list.remove(candidate)
counter += 1
if counter > 1:
return candidate_list
else:
return candidate_list
candidate_list = search(numbers)
fin = int(''.join(map(str, sorted(divisible + candidate_list, reverse=True))))

Merge sort implementation in python giving incorrect result

I am trying to implement the merge sort algorithm described in these notes by Jeff Erickson on page 3. but even though the algorithm is correct and my implementation seems correct, I am getting the input list as output without any change. Can someone point out the anomalies, if any, in it.
def merge(appnd_lst, m):
result = []
n = len(appnd_lst)
i, j = 0, m
for k in range(0, n):
if j < n:
result.append(appnd_lst[i])
i += 1
elif i > m:
result.append(appnd_lst[j])
j += 1
elif appnd_lst[i] < appnd_lst[j]:
result.append(appnd_lst[i])
i += 1
else:
result.append(appnd_lst[j])
j += 1
return result
def mergesort(lst):
n = len(lst)
if n > 1:
m = int(n / 2)
left = mergesort(lst[:m])
right = mergesort(lst[m:])
appnd_lst = left
appnd_lst.extend(right)
return merge(appnd_lst, m)
else:
return lst
if __name__ == "__main__":
print mergesort([3, 4, 8, 0, 6, 7, 4, 2, 1, 9, 4, 5])
There are three errors in your merge function a couple of indexing errors and using the wrong comparison operator. Remember python list indices go from 0 .. len(list)-1.
* ...
6 if j > n-1: # operator wrong and off by 1
* ...
9 elif i > m-1: # off by 1
* ...

Python - Memoization and Collatz Sequence

When I was struggling to do Problem 14 in Project Euler, I discovered that I could use a thing called memoization to speed up my process (I let it run for a good 15 minutes, and it still hadn't returned an answer). The thing is, how do I implement it? I've tried to, but I get a keyerror(the value being returned is invalid). This bugs me because I am positive I can apply memoization to this and get this faster.
lookup = {}
def countTerms(n):
arg = n
count = 1
while n is not 1:
count += 1
if not n%2:
n /= 2
else:
n = (n*3 + 1)
if n not in lookup:
lookup[n] = count
return lookup[n], arg
print max(countTerms(i) for i in range(500001, 1000000, 2))
Thanks.
There is also a nice recursive way to do this, which probably will be slower than poorsod's solution, but it is more similar to your initial code, so it may be easier for you to understand.
lookup = {}
def countTerms(n):
if n not in lookup:
if n == 1:
lookup[n] = 1
elif not n % 2:
lookup[n] = countTerms(n / 2)[0] + 1
else:
lookup[n] = countTerms(n*3 + 1)[0] + 1
return lookup[n], n
print max(countTerms(i) for i in range(500001, 1000000, 2))
The point of memoising, for the Collatz sequence, is to avoid calculating parts of the list that you've already done. The remainder of a sequence is fully determined by the current value. So we want to check the table as often as possible, and bail out of the rest of the calculation as soon as we can.
def collatz_sequence(start, table={}): # cheeky trick: store the (mutable) table as a default argument
"""Returns the Collatz sequence for a given starting number"""
l = []
n = start
while n not in l: # break if we find ourself in a cycle
# (don't assume the Collatz conjecture!)
if n in table:
l += table[n]
break
elif n%2 == 0:
l.append(n)
n = n//2
else:
l.append(n)
n = (3*n) + 1
table.update({n: l[i:] for i, n in enumerate(l) if n not in table})
return l
Is it working? Let's spy on it to make sure the memoised elements are being used:
class NoisyDict(dict):
def __getitem__(self, item):
print("getting", item)
return dict.__getitem__(self, item)
def collatz_sequence(start, table=NoisyDict()):
# etc
In [26]: collatz_sequence(5)
Out[26]: [5, 16, 8, 4, 2, 1]
In [27]: collatz_sequence(5)
getting 5
Out[27]: [5, 16, 8, 4, 2, 1]
In [28]: collatz_sequence(32)
getting 16
Out[28]: [32, 16, 8, 4, 2, 1]
In [29]: collatz_sequence.__defaults__[0]
Out[29]:
{1: [1],
2: [2, 1],
4: [4, 2, 1],
5: [5, 16, 8, 4, 2, 1],
8: [8, 4, 2, 1],
16: [16, 8, 4, 2, 1],
32: [32, 16, 8, 4, 2, 1]}
Edit: I knew it could be optimised! The secret is that there are two places in the function (the two return points) that we know l and table share no elements. While previously I avoided calling table.update with elements already in table by testing them, this version of the function instead exploits our knowledge of the control flow, saving lots of time.
[collatz_sequence(x) for x in range(500001, 1000000)] now times around 2 seconds on my computer, while a similar expression with #welter's version clocks in 400ms. I think this is because the functions don't actually compute the same thing - my version generates the whole sequence, while #welter's just finds its length. So I don't think I can get my implementation down to the same speed.
def collatz_sequence(start, table={}): # cheeky trick: store the (mutable) table as a default argument
"""Returns the Collatz sequence for a given starting number"""
l = []
n = start
while n not in l: # break if we find ourself in a cycle
# (don't assume the Collatz conjecture!)
if n in table:
table.update({x: l[i:] for i, x in enumerate(l)})
return l + table[n]
elif n%2 == 0:
l.append(n)
n = n//2
else:
l.append(n)
n = (3*n) + 1
table.update({x: l[i:] for i, x in enumerate(l)})
return l
PS - spot the bug!
This is my solution to PE14:
memo = {1:1}
def get_collatz(n):
if n in memo : return memo[n]
if n % 2 == 0:
terms = get_collatz(n/2) + 1
else:
terms = get_collatz(3*n + 1) + 1
memo[n] = terms
return terms
compare = 0
for x in xrange(1, 999999):
if x not in memo:
ctz = get_collatz(x)
if ctz > compare:
compare = ctz
culprit = x
print culprit

Categories