How would you go about this:
Write your own infinite precision "sum", "product", and "to the power of" functions, that represent numbers as lists of
digits between 0 and 9 with least significant digit first.
Thus: 0 is represented as the empty list [], and 10 is represented as [0,1].
You may assume that numbers are non-negative (no need for negative numbers, or for subtraction).
I have functions to convert to and from.
eg:
iint(5387) == [7, 8, 3, 5] and pint([7, 8, 3, 5]) == 5387
def iint(n):
# list of all digits in the int
digits = [int(x) for x in str(n)]
# reverse the list
digits.reverse()
return digits
def pint(I):
# new int c
c = 0
# iterates through list
for i in range(len(I)):
# add to c digit in the list multiplied by 10^of its position in the list. 1, 10, 100, 1000 ect.
c = c + I[i] * (10 ** i)
return c
# add two infinite integers
def iadd(I, J):
pass
First though would be just convert back to int do the calculation and then back again but that would "gut the question".
Not looking for a complete solution just some pointers on where to start for iadd()because I am completely stumped. I assume after you get iadd() the rest should be simple enough.
For writing your iadd function, one way is to use test-driven development; write your test inputs, and your expected outputs. assert that they're equal, then rewrite your function so it passes the testcase.
In the particular case of needing to add two lists of numbers together, "how would you do that by hand?" (as noted by a comment) is an excellent place to start. You might
starting from the least-significant digit
add individual digits together (including a carry from the previous digit, if any)
carry the "high" digit if the result is > 9
record the result of that addition
loop from step 2 until you exhaust the shorter number
if you have a carry digit "left over," handle that properly
if one of the input numbers has more digits than the other, properly handle the "left over" digits
Here's a code snippet that should help give some ideas:
for d in range(min(len(I),len(J))):
added = I[d] + J[d]
digit = added%10 + carry
carry = added//10
And some testcases to try:
assert iadd([1], [1]) == [2] # 1 + 1 == 2
assert iadd([0,1], [1]) == [1,1] # 10 + 1 == 11
assert iadd([9,1], [1]) == [0,2] # 19 + 1 == 20
assert iadd([9,9,9,9,9], [2]) == [1,0,0,0,0,1] # 99,999 + 2 == 100,001
assert iadd([4,0,2], [9,2,3,4,1]) == [3,3,5,4,1] # 201 + 14,329 == 14,533
Itertools's zip_longest should be very useful to implement the addition operation.
For example:
def iint(N): return [int(d) for d in reversed(str(N))]
def pint(N): return int("".join(map(str,reversed(N))))
from itertools import zip_longest
def iadd(A,B):
result = [0]
for a,b in zip_longest(A,B,fillvalue=0):
result[-1:] = reversed(divmod(a+b+result[-1],10))
while result and not result[-1]: result.pop(-1) # remove leading zeros
return result
a = iint(1234)
b = iint(8910)
print(iadd(a,b)) # [4, 4, 1, 0, 1] (10144)
For the multiplication, you should make sure to keep the intermediate results below 100
def iprod(A,B):
result = []
for iA,a in enumerate(A):
if not a: continue
result = iadd(result,[0]*iA+[a*d for d in B]) # a*d+9 <= 90
return result
print(iprod(a,b)) # [0, 4, 9, 4, 9, 9, 0, 1] 10994940
For the power operation, you'll want to break down the process into a reasonable number of multiplications. This can be achieved by decomposing the exponent into powers of 2 and multiplying the result by the compounded squares of the base (for 1 bits). But you'll need to make a division by 2 function to implement that.
This strategy is based on the fact that multiplying a base raised to various powers, adds these powers:
A^7 * A^6 = A^13
and that any number can be expressed as the sum of powers of two:
13 = 1 + 4 + 8,
so
A^13 = A^1 * A^4 * A^8.
This reduces the number of multiplications for A^B down to 2log(B) which is much less than multiplying A by itself B-1 times (although we'll be dealing with larger numbers).
def idiv2(N):
result = N.copy() or [0]
carry = 0
for i,d in enumerate(reversed(N),1):
result[-i],carry = divmod(result[-i]+carry*10,2)
return result if result[-1] else result[:-1]
def ipow(A,B):
result, a2n = [1], [] # a2n is compounded squares A, A^2, A^4, A^8, ...
while B:
a2n = iprod(a2n,a2n) if a2n else A
if B[0]%2 : result = iprod(result,a2n)
B = idiv2(B)
return result
print(ipow(iint(12),iint(13)))
# [2, 7, 0, 9, 7, 3, 5, 0, 2, 3, 9, 9, 6, 0, 1] 106993205379072
print(len(ipow(a,b))) # 27544 digits (takes a long time)
Further optimization could be achieved by creating a specialized square function and using it instead of iprod(a2n,a2n)
I'm having a difficult time understanding what's happening in these two examples I found of the Kadane Algorithm. I'm new to Python and I'm hoping understanding this complex algo will help me see/read programs better.
Why would one example be better than the other, is it just List vs Range? Is there something else that makes one of the examples more efficient? Also, some questions about what's happening in the calculations. (questions inside the examples)
I've used PythonTutor to help me get a visual on what exactly is happening step by step.
Example 1:
In PythonTuter, when you select next step in the screen shot provided, The value of so_far turns to 1. How is this? Giving the sum, I've thought its adding -2 + 1 which is -1, so when so_far turns to 1, how is this?
def max_sub(nums):
max_sum = 0
so_far = nums[0]
for x in nums[1:]:
so_far = max(x, x + so_far)
max_sum = max(so_far, max_sum)
return max_sum
nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
max_sub(nums)
6
Example 2:
Similar question for this one, when I select NEXT step, the max_sum turns from -2 to 4... but how so if it's adding the element in the 2 (which is 4). To me, that would be -2 + 4 = 2 ?
def maxSubArraySum(a,size):
max_so_far =a[0]
curr_max = a[0]
for i in range(1,size):
curr_max = max(a[i], curr_max + a[i])
max_so_far = max(max_so_far,curr_max)
return max_so_far
a = [-2, -3, 4, -1, -2, 1, 5, -3]
print("Maximum contiguous sum is" , maxSubArraySum(a,len(a)))
Maximum contiguous sum is 7
So, this would be a 2 part question than:
[1]Based on understandings, why would one be more pythonic and more efficient than the other?
[2]How can I better understand the calculations happening in the examples?
Simply watch each step and you could figure out this problem:
[Notes] this program seems to work based on the assumption of mixed integer numbers? only positive and negatives.
# starting
so_far = -2 # init. to nums[0]
max_sum = 0
# in the for-loop:
x = 1 # starting with nums[1:]
so_far = max(1, -1) -> 1 (x is 1, -2 + 1)
max_sum = max(0, 1) -> 1
..... continue .... each step is to find the max accumulated numbers sum, as it's evident in the max( ) statement. *There is no `sum` involved, except it tried to determine the current x is good (not negative) then so add it to the so_far.
More performance measurement data points to compare these two different approaches shown that first example is definitely faster ~22-24% faster than 2nd one with input size of 2k.
if __name__ == '__main__':
L = list(range(-1_000, 1_000, 1))
random.shuffle(L)
baseTestCase = partial(max_sub, nums=L)
print(timeit.timeit(baseTestCase, number=100_000)) # 86.0588067
rangeTestCase = partial(max_SubArraySum, a=L, size=len(L))
print(timeit.timeit(rangeTestCase, number=100_000)) # 105.415955
For instance, given list [1, 0, 1] the code would return [1,1,0]. Other examples:
[1,1,1] -- > [1,0,0,0]
[1,0,0,1] --> [1,0,1,0]
I'm having most trouble understanding what my base case for recursion would be and then how to implement for the (n-1) case.
def increment_helper(number):
newNum = []
if len(number) ==1:
if number[0] == 1:
carry = 1
newNum.append(0)
else:
carry = 0
newNum.append(1)
else:
return increment_helper(number-1)
return newNum
So I'm sure that there are a lot of errors in here specifically how I am calling my recursion because I am not sure how to recurse on the list while storing the number that is removed somehow. The else return statement is obviously incorrect but I am using that as a placeholder. I am unsure of what condition to use as my base case for incrementation. I think I should be using a carry variable that keeps track of whether I am carrying a one over but other than that I am stuck on how to proceed.
Ah, ha! Okay, you have some idea of what you're doing. The basic outline is
Base case: how do I know when I'm done?
You're done when you run out of digits. number should be a list of individual digits; check its length to figure out when not to recur.
Recursion case: what next?
The general concept of recursion is "do something simple, reduce the problem by a small amount, and recur with the smaller problem." Your job in this part is to do the addition for one digit. If you need to keep going (is there a carry from that digit?), then recur. Otherwise, you have all the info you need to finish.
Specific application
Your recursion step will involve calling increment_helper with one digit less: not number - 1, but number[:-1].
After you return from each recursion, you'lll then want to append the digit you just finished. For instance, if you're incrementing 1101, your first call will see that the right-hand one, incremented, has a carry. The new digit is 0, and you have to recur. Hold onto the 0 for a moment, call yourself with 110, and get the result of that call. Append your saved 0 to that, and return to your main program.
Does that get you moving?
This one is "tricky" because you have two things going at each step:
what is the current number I'm looking at? (binary question 0/1)
should it be incremented? (do we have carry?) (binary question yes/no)
This leads to 4 cases.
There is the extra "case" of did we even get a list but it isn't that interesting.
So I would describe it as follows:
if not carry:
# technically meaningless so just return the entire list immediately
return list
# we have "carry", things will change
if not list:
# assumes [] == [0]
return [1]
if list[0]:
# step on `1` (make it 0 and carry)
return [0] + increment(rest_of_list)
# step on `0` (make it 1 and no more carry)
return [1] + rest_of_list
I strongly advise to change lists to tuples and work with these.
Also note that the recursion is on the reversed list, so apply as follows:
def increment_helper(lst, carry):
if not carry:
return lst
if not lst:
return [1]
if lst[0]:
return [0] + increment_helper(lst[1:], True)
return [1] + lst[1:]
def increment(lst):
# long but really just reverse input and output
return increment_helper(lst[::-1], True)[::-1]
I used some shortcuts by returning lists immediately (short-circuiting), but you can make it more "pure" by carrying on the recursion even without carry.
Another recursive approach.
Is the current input an empty list?
If yes return [1]
If no, continue
Is the sum (value) of the last element in the list and 1 greater than 1?
If so recursively call your function on the list without the last element (number_list[:-1]) and append [0] to the result.
If no, set the last element of the list to the sum.
Return the number_list
Code:
def increment_helper(number_list):
if not number_list:
return [1]
value = number_list[-1] + 1
if value > 1:
number_list = increment_helper(number_list[:-1]) + [0]
else:
number_list[-1] = value
return number_list
Example output:
numbers = [[1, 0, 1], [1,1,1], [1,0,0,1]]
for n in numbers:
print("%r ---> %r" % (n, increment_helper(n)))
#[1, 0, 1] ---> [1, 1, 0]
#[1, 1, 1] ---> [1, 0, 0, 0]
#[1, 0, 0, 1] ---> [1, 0, 1, 0]
Try this:
def add1(listNum):
if listNum.count(0):
oneArr = [[0] * (len(listNum) - 1)] + [1]
sumArr = []
for i in range(len(listNum)):
sumArr.append(sum(listNum[i], oneArr[i]))
newArr = []
for j in range(len(sumArr) - 1):
if sumArr[len(sumArr) - 1 - j] < 2:
newArr.insert(0, sumArr[len(sumArr) - 1 - j])
else:
newArr.insert(0, 1)
sumArr[len(sumArr) - 1 - j] += 1
return sumArr
else:
return [1] + [[0] * len(listNum)]
There aren't many reasons for using recursion for a program as simple as this, which is why I have chosen to provide a non-recursive solution.
In case it interests you, I've calculated the time complexity of this function and it is O(n).
It may be best to use two functions: one to check if a simple increment of the last position would suffice and another to perform the recursion should the previous attempt fail:
vals = [[1, 0, 1], [1,1,1], [1,0,0,1]]
def update_full(d):
if all(i in [1, 0] for i in d):
return d
start = [i for i, a in enumerate(d) if a > 1]
return update_full([1]+[0 if i > 1 else i for i in d] if not start[0] else [a+1 if i == start[0] -1 else 0 if i == start[0] else a for i, a in enumerate(d)])
def increment(d):
if not d[-1]:
return d[:-1]+[1]
return update_full(d[:-1]+[2])
print(list(map(increment, vals)))
Output:
[[1, 1, 0], [1, 0, 0, 0], [1, 0, 1, 0]]
You can treat the (binary) digits recursively by traversing the number from tail to head (just like addition works).
Before performing the recursion you have to check for two special cases:
No increment at the current digit is to be performed. Then just return the unmodified digits.
A single digit remains (you're at the head of the number). Then you possibly need to append the overflow to the head.
For the remaining cases you can increment the current digit and treat all digits before the current one in a recursive manner.
def bin_incr(n, incr=True):
if not incr:
return n
if len(n) == 1:
return [1, 0] if n[0] == 1 else [1]
return (
# `n[-1] == 1` denotes an overflow to the next digit.
bin_incr(n[:-1], n[-1] == 1)
+ [(n[-1] + 1) % 2]
)
I understand you don't want to use decimal addition, but if you must use big endian bit order, converting back to decimal first is probably the most practical. Otherwise, you end up with unnecessary reverse calls or awkward negative array indices
def binary (dec): # big endian bit order
if dec < 2:
return [ dec ]
else:
return binary (dec >> 1) + [ dec & 1 ]
def decimal (bin, acc = 0):
if not bin:
return acc
else:
return decimal (bin[1:], (acc << 1) + bin[0])
def increment (bin):
# sneaky cheat
return binary (decimal (bin) + 1)
for x in range (10):
print (x, binary (x), increment (binary (x)))
# 0 [0] [1]
# 1 [1] [1, 0]
# 2 [1, 0] [1, 1]
# 3 [1, 1] [1, 0, 0]
# 4 [1, 0, 0] [1, 0, 1]
# 5 [1, 0, 1] [1, 1, 0]
# 6 [1, 1, 0] [1, 1, 1]
# 7 [1, 1, 1] [1, 0, 0, 0]
# 8 [1, 0, 0, 0] [1, 0, 0, 1]
# 9 [1, 0, 0, 1] [1, 0, 1, 0]
If however you can represent your binary numbers in little endian bit order, things change. Instead of converting back to decimal, increment can be defined directly as a beautiful recursive function
def binary (dec): # little endian bit order
if dec < 2:
return [ dec ]
else:
return [ dec & 1 ] + binary (dec >> 1)
def increment (bin):
if not bin:
return [1]
elif bin[0] == 0:
return [1] + bin[1:]
else:
return [0] + increment(bin[1:])
for x in range (10):
print (x, binary (x), increment (binary (x)))
# 0 [0] [1]
# 1 [1] [0, 1]
# 2 [0, 1] [1, 1]
# 3 [1, 1] [0, 0, 1]
# 4 [0, 0, 1] [1, 0, 1]
# 5 [1, 0, 1] [0, 1, 1]
# 6 [0, 1, 1] [1, 1, 1]
# 7 [1, 1, 1] [0, 0, 0, 1]
# 8 [0, 0, 0, 1] [1, 0, 0, 1]
# 9 [1, 0, 0, 1] [0, 1, 0, 1]
Aside: converting the little endian representation back to decimal is a little different. I provide this to show that use cases for recursion exist everywhere
def decimal (bin, power = 0):
if not bin:
return 0
else:
return (bin[0] << power) + decimal (bin[1:], power + 1)
This part of the answer gives you cake and allows you to eat it too. You get big endian bit order and a recursive increment that steps through the bits in left-to-right order – You should use either implementation above for a number of reasons, but this aims to show you that even though your problem is complex, it's still possible to think about it recursively. No reverse or arr[::-1] was misused in the making of this function.
def binary (dec): # big endian bit order
if dec < 2:
return [ dec ]
else:
return binary (dec >> 1) + [ dec & 1 ]
def increment (bin, cont = lambda b, carry: [1] + b if carry else b):
if bin == [0]:
return cont ([1], 0)
elif bin == [1]:
return cont ([0], 1)
else:
n, *rest = bin
return increment (rest, lambda b, carry:
cont ([n ^ carry] + b, n & carry))
for x in range (10):
print (x, binary (x), increment (binary (x)))
# 0 [0] [1]
# 1 [1] [1, 0]
# 2 [1, 0] [1, 1]
# 3 [1, 1] [1, 0, 0]
# 4 [1, 0, 0] [1, 0, 1]
# 5 [1, 0, 1] [1, 1, 0]
# 6 [1, 1, 0] [1, 1, 1]
# 7 [1, 1, 1] [1, 0, 0, 0]
# 8 [1, 0, 0, 0] [1, 0, 0, 1]
# 9 [1, 0, 0, 1] [1, 0, 1, 0]
We start by breaking the problem up into smaller parts; n is the first problem, and rest is the rest of the problems. But the key to thinking with continuations (like cont above) is to think big.
In this particular problem, n gets updated based on whether rest gets updated. So we immediately recur on rest and pass a continuation that will receive the result of the subproblem. Our continuation receives the answer to the subproblem b, and whether or not that subproblem results in a carry.
...
else:
n, *rest = bin
return increment (rest, lambda b, carry:
cont ([n ^ carry] + b, n & carry))
The n ^ carry and n & carry expressions determine what the answer to this subproblem is and what the next carry will be. The following truth table shows that ^ and & encodes our answer and next_carry respectively. For example, if n is 0 and carry is 1, the carry can be consumed. The answer will be [1] + the answer to the subproblem and the next carry will be 0.
n carry (answer, next_carry) n ^ carry n & carry
0 0 ([0] + b, 0) 0 0
0 1 ([1] + b, 0) 1 0
1 0 ([1] + b, 0) 1 0
1 1 ([0] + b, 1) 0 1
The base cases are simple. If the subproblem is [0], the answer is [1] and no carry of 0. If the subproblem is [1], then the answer is [0]with a carry of 1
...
if bin == [0]:
return cont ([1], 0)
elif bin == [1]:
return cont ([0], 1)
Lastly, design the default continuation – if the answer to the problem b results in a carry, simply prepend [1] to the answer, otherwise just return the answer.
cont = lambda b, carry: [1] + b if carry else b
You are asking for the increment/successor/next function that will generate a sequence of sequences. Since other have given code, I will give a general method for developing such functions.
First, develop a multiple recursion (2 or more recursive calls) for calculating, say, all sequences of the type of length N. For binary sequences (bs) in big-endian order, from N 0s to N 1s, the base case bs(0) expression is [[]], the sequence that contains the one sequence with no binary digits. The double recursion for bs(n) in terms of bs(n-1) is ([0] concatenated to all members of bs(n-1) (in order)) plus ([1] contanenated to all members of bs(n-1)).
Next, focus on the transition between the subsequences returned by adjacent recursive calls. Here there is just one: 0, 1, ..., 1 to 1, 0, ..., 0. To increment across this boundary, we need to replace 0 followed by 0 or more 1s by 1 followed by the same number of 0s. We find such breaks by scanning from the right for the first 0, as others have shown.
It turns out the every increment crosses the boundary between adjacent bs(k) calls for some value of k, which is to say, at some level of the tree of calls resulting from double recursion.
So far, that I know of, the same idea works for designing the increment function for sequences of grey codes, sequences of conbinations (n things taken k at a time), and sequences of permutations.
Note 1: the 1->0 transitions can be done 1 at a time or all at once.
Note 2: the binary bit testing and flipping is the turing machine algorithm for count + 1. Stephen Wolfram, A New Kind of Science, presents, as I remember, 3 different implementations in the TM chapter.
Note 3 (added in edit): If one switches from prepending 0 first to prepending 1 first, or from prepending to appending, or both, one gets 3 other sequence of sequences, with 3 other increment functions.
You do not need recursion in this case. Let us start with the simplest implementation:
implement a full adder which will operate on bits.
implement a ripple adder using the full adder which will operate on 2 lists of bits.
The full adder implementation is straight forrward.
def full_adder(a,b,cin):
sum_ = a^(b^cin)
cout = (a&b) | (a|b)&cin
return sum_, cout
Tests to make sure the full adder conforms to the specs:
>>> zero_one = (0,1)
>>> [full_adder(*x) for x in [(a,b,c) for a in zero_one for b in zero_one for c in zero_one]]
[(0, 0), (1, 0), (1, 0), (0, 1), (1, 0), (0, 1), (0, 1), (1, 1)]
Since the parameters of the ripple adder are lists, we need to ensure the list lengths match before the addition. This is done by padding the shorter list with leading zeros.
def ripple_adder(xs,ys):
x, y = map(len, (xs, ys))
alen = max(x, y)
ax, by = map(lambda f: f if len(f) == alen else [0]*(alen-len(f)) + f, (xs, ys))
cout = 0
res = [0]*(alen)
for i in range(alen-1, -1, -1):
a, b, cin = ax[i], by[i], cout
s, cout = full_adder(a, b, cin)
res[i] = s
if cout:
res = [1] + res
return res
Finally, we define bin_inc the binary increment function in terms of the ripple adder
def bin_inc(bin_lst):
return ripple_adder(bin_lst, [1])
>>> bin_inc([1,1,1])
[1, 0, 0, 0]
>>> bin_inc([1,0,0,1])
[1, 0, 1, 0]
Now for a solution that is simpler but requires a little insight. Consider the following
obervations for an input xs with length L and output ys
if xs is all ones, ys will have length L+1, the first element of ys will be 1 and the rest will be zeros.
if xs has a zero element, let p be the index of the last zero element in xs.
Then ys will be the list consisting of the first p elements of xs followed by a 1 followed by zeros of length L - p. That is
ys = xs[:p] + [1] + [0]*(L-p).
We can translate this to python code easily. We use python's list.index method to find the last occurence of zero by working on the reverse of the list and adjust the algorithm appropriately:
def bin_inc_2(xs):
if all(xs):
return [1] + [0]*len(xs)
p = xs[::-1].index(0)
return xs[:-p-1] + [1] + [0]*p
This works and is simpler than the ripple_adder based implementation. One minor drawback you might notice is when xs has a zero element, we traverse it to check if it is all ones, then traverse it again to find the first occurence of zero.
We can simplify the implementation and make it more pythonic atthe same time by using a try except block:
def bin_inc_3(bin_lst):
try:
p = bin_lst[::-1].index(0)
return bin_lst[:-p-1] + [1] + [0]*p
except ValueError:
return [1] + [0]*len(bin_lst)
This implementation is simple in terms of source text, and idiomatic python. Now we test it against the ripple_adder based adder to make sure it works well.
>>> z_n = (0,1)
>>> xs = [[a,b,c,d,e,f,g,h] for a in z_n for b in z_n for c in z_n for d in z_n
for e in z_n for f in z_n for g in z_n for h in z_n ]
>>> print(all(ripple_adder(x, [1]) == bin_inc_3(x) for x in xs))
True
Fantastic, it works as intended and correctly handles leading zeros as evidenced by the tests (which increments every number from 0 to 255).
There's only a need to recurse when there is a carry:
def f(n):
# Base cases
if not n:
return [1]
if n == [1]:
return [1, 0]
if n[-1] == 0:
return n[:-1] + [1]
if n[-2:] == [0, 1]:
return n[:-2] + [1, 0]
# Recurse
return f(n[:-2]) + [0, 0]
numbers = [[1, 0, 1], [1,1,1], [1,0,0,1], [1, 0, 1, 1]]
for n in numbers:
print n, f(n)
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
If I need to divide for example 7 into random number of elements of random size, how would I do this?
So that sometimes I would get [3,4], sometimes [2,3,1] and sometimes [2,2,1,1,0,1]?
I guess it's quite simple, but I can't seem to get the results. Here what I am trying to do code-wise (does not work):
def split_big_num(num):
partition = randint(1,int(4))
piece = randint(1,int(num))
result = []
for i in range(partition):
element = num-piece
result.append(element)
piece = randint(0,element)
#What's next?
if num - piece == 0:
return result
return result
EDIT: Each of the resulting numbers should be less than initial number and the number of zeroes should be no less than number of partitions.
I'd go for the next:
>>> def decomposition(i):
while i > 0:
n = random.randint(1, i)
yield n
i -= n
>>> list(decomposition(7))
[2, 4, 1]
>>> list(decomposition(7))
[2, 1, 3, 1]
>>> list(decomposition(7))
[3, 1, 3]
>>> list(decomposition(7))
[6, 1]
>>> list(decomposition(7))
[5, 1, 1]
However, I am not sure if this random distribution is perfectly uniform.
You have to define what you mean by "random". If you want an arbitrary integer partition, you can generate all integer partitions, and use random.choice. See python: Generating integer partitions This would give no results with 0. If you allow 0, you will have to allow results with a potentially infinite number of 0s.
Alternatively if you just want to take random chunks off, do this:
def arbitraryPartitionLessThan(n):
"""Returns an arbitrary non-random partition where no number is >=n"""
while n>0:
x = random.randrange(1,n) if n!=1 else 1
yield x
n -= x
It is slightly awkward due to the problem constraints that each number should be less than the original number; it would be more elegant if you allowed the original number. You can do randrange(n) if you want 0s but it wouldn't make sense unless there is a hidden reason you are not sharing.
edit in response to question edit: Since you desire the "the number of zeroes should be no less than number of partitions" you can arbitrarily add 0s to the end:
def potentiallyInfiniteCopies(x):
while random.random()<0.5:
yield x
x = list(arbitraryPartitionLessThan(n))
x += [0]*len(x) + list(potentiallyInfiniteCopies(0))
The question is quite arbitrary, and I highly recommend that you choose this instead as your answer:
def arbitraryPartition(n):
"""Returns an arbitrary non-random partition"""
while n>0:
x = random.randrange(1,n+1)
yield x
n -= x
Recursion to the rescue:
import random
def splitnum(num, lst=[]):
if num == 0:
return lst
n = random.randint(0, num)
return splitnum(num - n, lst + [n])
for i in range(10):
print splitnum(7)
Result:
[1, 6]
[6, 0, 0, 1]
[5, 1, 1]
[6, 0, 1]
[2, 0, 3, 1, 1]
[7]
[2, 1, 0, 4]
[7]
[3, 4]
[2, 0, 4, 1]
This solution does not insert 0s (I do not understand what your description of your zeros rule is supposed to be), and is equally likely to generate every possible combination other than the original number by itself.
def split (n):
answer = [1]
for i in range(n - 1):
if random.random() < 0.5:
answer[-1] += 1
else:
answer.append(1)
if answer == [n]:
return split(n)
else:
return answer