Issue with recursive function in python - python

I have an issue with defining this function recursively. My goal is for the function to return the maximal income from a given n. n (in meters) here is the amount of cloth. The h list is the profit when selling a rug that is n meters long. For example, h[2]=5 is the profit when making a 2 meters long carpet which is for simplicity 5 dollars, and h[0]=0 since no cloth is available and no product produced. So the h list is just the market value of rugs of different lengths. I want the function to return the maximum profit if I have n meters of cloth. The prices are only defined for carpets up to 4 m, hence why the h list is what it is. The recursion for n=2 for example calculates the value of making a rug 2 m big and compares the profit with making two 1 m carpets and so on.
The recursion is loosely stated as:
income(0) = 0
income(n) = max(h[i]+income(n-i)) for 1<=i<=n
As of now, by the code below, I'm getting a recursion limit exceeded. Which I don't find weird as I have no condition for limiting the loop but the issue is I don't know how to proceed and define some sort of constraint. I realize the description here might be very confusing but please ask and I'll try to explain more extensively.
def income(n):
h = [0,2,5,6,9]
for i in range(n):
return max(h[i]+income(n-i))
EDIT:
This is my solution I came up with.
def income(n):
"""Returns maximum profit by a given n."""
h = [2,5,6,9,0]
m = []
if n == 0:
return 0
else:
for i in range(n):
m.append(h[i]+income(n-i-1))
return max(m)
With cache:
def memoize(f):
memo = {}
def helper(x):
if x not in memo:
memo[x] = f(x)
return memo[x]
return helper
income = memoize(income)

The problem you're trying to solve is a version of the unbounded knapsack problem. Instead of trying to maximize the value you can fit into a pack, you're trying to maximize the value the rugs you can make with your cloth. Instead of caring about item weight, you care about how much of your cloth gets used in the rug. But it works out the same. In any case, that's a very hard problem to solve efficiently for arbitrary inputs, but you can brute force it pretty easily for small n values.
You correctly identified that your recursion continues forever (or rather until the recursion limit) because you've not written a base case. It's actually a bit worse than that, since the first recursion is with i equal to 0, which calls the function again with the exact same n you were already trying to solve (since n-i is n when i==0).
You really don't want to recurse when i is zero, since you won't have reduced the scope of the problem at all. The simplest approach is probably just to modify your range to start i at 1, and increase from there. You also need some logic to prevent the loop from going past index 4, since you don't have any more values in the list for longer lengths.
def income(n):
if n == 0: # base case
return 0
h = [0,2,5,6,9]
return max(h[i]+income(n-i) for i in range(1, min(n+1, len(h)))) # recursive case
In addition to adding a base case for n==0 and fixing the range, I also replaced the loop with a generator expression that does the recursion. This is necessary because max expects either several arguments (which it compares against each other), or a single iterable argument (who's values get compared). You were passing just a single value, which doesn't work.
The version above works just fine for small n values, up to about 15. But going higher than that begins to take longer and longer times because the recursion gets very deep and broad, and you end up calculating a lot of values over and over. This is what I meant when I said the knapsack problem is hard!
One trick that will speed things up a whole lot is to cache the results of each computation the first time you figure it out, and use the cached value if its needed again later.
_cache = {0: 0} # the cache is pre-seeded to handle our base case of n=0
def income(n):
if n not in _cache:
h = [0,2,5,6,9]
_cache[n] = max(h[i]+income(n-i) for i in range(1, min(n+1, len(h))))
return _cache[n]
I'd further note that for your specific h list, it looks like it is always best to make as many 2 meter rugs as you can, plus up to one 1 meter rug to use up the last meter of cloth if you started with an odd n. So if you're willing to hard code this optimal solution to the very specific problem in your example, you could skip the recursion and just do:
def income(n):
return n//2 * 5 + n%2 * 2

Related

Find all combinations of positive integers in increasing order that adds up to a given positive number n

How to write a function that takes n (where n > 0) and returns the list of all combinations of positive integers that sum to n?
This is a common question on the web. And there are different answers provided such as 1, 2 and 3. However, in the answers provided, they use two functions to solve the problem. I want to do it with only one single function. Therefore, I coded as follows:
def all_combinations_sum_to_n(n):
from itertools import combinations_with_replacement
combinations_list = []
if n < 1:
return combinations_list
l = [i for i in range(1, n + 1)]
for i in range(1, n + 1):
combinations_list = combinations_list + (list(combinations_with_replacement(l, i)))
result = [list(i) for i in combinations_list if sum(i) == n]
result.sort()
return result
If I pass 20 to my function which is all_combinations_sum_to_n(20), the OS of my machine kills the process as it is very costly. I think the space complexity of my function is O(n*n!). How do I modify my code so that I don't have to create any other function and yet my single function has an improved time or space complexity? I don't think it is possible by using itertools.combinations_with_replacement.
UPDATE
All answers provided by Barmar, ShadowRanger and pts are great. As I was looking for an efficient answer in terms of both memory and runtime, I used https://perfpy.com and selected python 3.8 to compare the answers. I used six different values of n and in all cases, ShadowRanger's solution had the highest score. Therefore, I chose ShadowRanger's answer as the best one. The scores were as follows:
You've got two main problems, one causing your current problem (out of memory) and one that will continue the problem even if you solve that one:
You're accumulating all combinations before filtering, so your memory requirements are immense. You don't even need a single list if your function can be a generator (that is iterated to produce a value at a time) rather than returning a fully realized list, and even if you must return a list, you needn't generate such huge intermediate lists. You might think you need at least one list for sorting purposes, but combinations_with_replacement is already guaranteed to produce a predictable order based on the input ordering, and since range is ordered, the values produced will be ordered as well.
Even if you solve the memory problem, the computational cost of just generating that many combinations is prohibitive, due to poor scaling; for the memory, but not CPU, optimized version of the code below, it handles an input of 11 in 0.2 seconds, 12 in ~2.6 seconds, and 13 in ~11 seconds; at that scaling rate, 20 is going to approach heat death of the universe timeframes.
Barmar's answer is one solution to both problems, but it's still doing work eagerly and storing the complete results when the complete work might not even be needed, and it involves sorting and deduplication, which aren't strictly necessary if you're sufficiently careful about how you generate the results.
This answer will fix both problems, first the memory issue, then the speed issue, without ever needing memory requirements above linear in n.
Solving the memory issue alone actually makes for simpler code, that still uses your same basic strategy, but without consuming all your RAM needlessly. The trick is to write a generator function, that avoids storing more than one results at a time (the caller can listify if they know the output is small enough and they actually need it all at once, but typically, just looping over the generator is better):
from collections import deque # Cheap way to just print the last few elements
from itertools import combinations_with_replacement # Imports should be at top of file,
# not repeated per call
def all_combinations_sum_to_n(n):
for i in range(1, n + 1): # For each possible length of combinations...
# For each combination of that length...
for comb in combinations_with_replacement(range(1, n + 1), i):
if sum(comb) == n: # If the sum matches...
yield list(comb) # yield the combination
# 13 is the largest input that will complete in some vaguely reasonable time, ~10 seconds on TIO
print(*deque(all_combinations_sum_to_n(13), maxlen=10), sep='\n')
Try it online!
Again, to be clear, this will not complete in any reasonable amount of time for an input of 20; there's just too much redundant work being done, and the growth pattern for combinations scales with the factorial of the input; you must be more clever algorithmically. But for less intense problems, this pattern is simpler, faster, and dramatically more memory-efficient than a solution that builds up enormous lists and concatenates them.
To solve in a reasonable period of time, using the same generator-based approach (but without itertools, which isn't practical here because you can't tell it to skip over combinations when you know they're useless), here's an adaptation of Barmar's answer that requires no sorting, produces no duplicates, and as a result, can produce the solution set in less than a 20th of a second, even for n = 20:
def all_combinations_sum_to_n(n, *, max_seen=1):
for i in range(max_seen, n // 2 + 1):
for subtup in all_combinations_sum_to_n(n - i, max_seen=i):
yield (i,) + subtup
yield (n,)
for x in all_combinations_sum_to_n(20):
print(x)
Try it online!
That not only produces the individual tuples with internally sorted order (1 is always before 2), but produces the sequence of tuples in sorted order (so looping over sorted(all_combinations_sum_to_n(20)) is equivalent to looping over all_combinations_sum_to_n(20) directly, the latter just avoids the temporary list and a no-op sorting pass).
Use recursion instead of generating all combinations and then filtering them.
def all_combinations_sum_to_n(n):
combinations_set = set()
for i in range(1, n):
for sublist in all_combinations_sum_to_n(n-i):
combinations_set.add(tuple(sorted((i,) + sublist)))
combinations_set.add((n,))
return sorted(combinations_set)
I had a simpler solution that didn't use sorted() and put the results in a list, but it would produce duplicates that just differed in order, e.g. [1, 1, 2] and [1, 2, 1] when n == 4. I added those to get rid of duplicates.
On my MacBook M1 all_combinations_sum_to_n(20) completes in about 0.5 seconds.
Here is a fast iterative solution:
def csum(n):
s = [[None] * (k + 1) for k in range(n + 1)]
for k in range(1, n + 1):
for m in range(k, 0, -1):
s[k][m] = [[f] + terms
for f in range(m, (k >> 1) + 1) for terms in s[k - f][f]]
s[k][m].append([k])
return s[n][1]
import sys
n = 5
if len(sys.argv) > 1: n = int(sys.argv[1])
for terms in csum(n):
print(' '.join(map(str, terms)))
Explanation:
Let's define terms as a non-empty, increasing (can contain the same value multiple times) list of postitive integers.
The solution for n is a list of all terms in increasing lexicographical order, where the sum of each terms is n.
s[k][m] is a list of all terms in increasing lexicographical order, where the sum of each terms in n, and the first (smallest) integer in each terms is at least m.
The solution is s[n][1]. Before returning this solution, the csum function populates the s array using iterative dynamic programming.
In the inner loop, the following recursion is used: each term in s[k][m] either has at least 2 elements (f and the rest) or it has 1 element (k). In the former case, the rest is a terms, where the sum is k - f and the smallest integer is f, thus it is from s[k - f][f].
This solution is a lot faster than #Barmar's solution if n is at least 20. For example, on my Linux laptop for n = 25, it's about 400 times faster, and for n = 28, it's about 3700 times faster. For larger values of n, it gets much faster really quickly.
This solution uses more memory than #ShadowRanger's solution, because this solution creates lots of temporary lists, and it uses all of them until the very end.
How to come up with such a fast solution?
Try to find a recursive formula. (Don't write code yet!)
Sometimes recursion works only with recursive functions with multiple variables. (In our case, s[k][m] is the recursive function, k is the obvious variable implied by the problem, and m is the extra variable we had to invent.) So try to add a few variables to the recursive formula: add the minimum number of variables to make it work.
Write your code so that it computes each recursive function value exactly once (not more). For that, you may add a cache (memoization) to your recursive function, or you may use dynamic programming, i.e. populating a (multidimensional) array in the correct order, so that what is needed is already populated.

Is it possible to compute median while data is still being generated? Python online median calculator

I've seen a broader version of this question asked, where the individual was looking for more than one summary statistic, but I have not seen a solution presented. I'm only interested in the median, in Python, here.
Let's say I'm generating a million values in a loop. I cannot save the million values into a list and compute median once I'm done, because of memory issues. Is it possible to compute median as I go? For mean, I'd just sum values progressively, and once done divide by a million. For median the answer does not seem as intuitive.
I'm stuck on the "thought experiment" section of this, so I haven't been able to really try anything that I think might work. I'm not sure if this is an algorithm that has been implemented, but I can't find it if it has been.
This won't be possible unless your idea of "values" is restricted in some exploitable way; and/or you can make more than one pass over the data; and/or you're willing to store stuff on disk too. Suppose you know there are 5 values, all distinct integers, and you know the first 3 are 5, 6, 7. The median will be one of those, but at this point you can't know which one, so you have to remember all of them. If 1 and 2 come next, the median is 5; if 4 and 8 next, 6; if 8 and 9 next, it's 7.
This obviously generalizes to any odd number of values range(i, i + 2*N+1), at the point you've seen the first N+1 of them: the median can turn out to be any one of those first N+1, so unless there's something exploitable about the nature of the values, you have to remember all of them at that point.
An example of something exploitable: you know there are at most 100 distinct values. Then you can use a dict to count how many of each appear, and easily calculate the median at the end from that compressed representation of the distribution.
Approximating
For reasons already mentioned, there is no "shortcut" here to be had in general. But I'll attach Python code for a reasonable one-pass approximation method, as detailed in "The Remedian: A Robust Averaging Method for Large Data Sets". That paper also points to other approximation methods.
The key: pick an odd integer B greater than 1. Then successive elements are stored in a buffer until B of them have been recorded. At that point, the median of those advances to the next level, and the buffer is cleared. Their median remains the only memory of those B elements retained.
The same pattern continues at deeper levels too: after B of those median-of-B medians have been recorded, the median of those advances to the next level, and the second-level buffer is cleared. The median advanced then remains the only memory of the B**2 elements that went into it.
And so on. At worst it can require storing B * log(N, B) values, where N is the total number of elements. In Python it's easy to code it so buffers are created as needed, so N doesn't need to be known in advance.
If B >= N, the method is exact, but then you've also stored every element. If B < N, it's an approximation to the median. See the paper for details - it's quite involved. Here's a case that makes it look very good ;-)
>>> import random
>>> xs = [random.random() for i in range(1000001)]
>>> sorted(xs)[500000] # true median
0.5006315438367565
>>> w = MedianEst(11)
>>> for x in xs:
... w.add(x)
>>> w.get()
0.5008443883489089
Perhaps surprisingly, it does worse if the inputs are added in sorted order:
>>> w.clear()
>>> for x in sorted(xs):
... w.add(x)
>>> w.get()
0.5021045181828147
User beware! Here's the code:
class MedianEst:
def __init__(self, B):
assert B > 1 and B & 1
self.B = B
self.half = B >> 1
self.clear()
def add(self, x):
for xs in self.a:
xs.append(x)
if len(xs) == self.B:
x = sorted(xs)[self.half]
xs.clear()
else:
break
else:
self.a.append([x])
def get(self):
total = 0
weight = 1
accum = []
for xs in self.a:
total += len(xs) * weight
accum.extend((x, weight) for x in xs)
weight *= self.B
# `total` elements in all
limit = total // 2 + 1
total = 0
for x, weight in sorted(accum):
total += weight
if total >= limit:
return x
def clear(self):
self.a = []

Python how to generate all pair-terms

I want an infinite generator of all "pair-terms". Where 0 is pair-term and a tuple (a,b) of two pair-terms is a pair term. It's only important that each item appears at least once (in a finite amount of time), but exactly once would be more efficient.
I came up with
def pairTerms():
yield 0
generated=[]
diagonal=-1 #sum indices in generated of the pairs we are generating, could be replaced by len(generated)-1
for t in pairTerms():
generated.append(t)
diagonal+=1
for i,a in enumerate(generated):
yield (a,generated[diagonal-i])
But this quickly fills up the memory.
EDIT: this approach actually seems to work good enough, generating over 10 million terms before fulling up the memory.
Alternatively:
def pairTermsDepth(depth):
yield 0
if depth:
for a in pairTermsDepth(depth-1):
for b in pairTermsDepth(depth-1):
yield (a,b)
def pairTerms():
i=0
while True:
for item in pairTermsDepth(i):
i+=1
yield item
But this has the disadvantage of re-listing all old terms when a new while iteration has been reached and exhausting the stack.
Note: I didn't quite know how to tag this question, feel free to change them.
The following approach can find the first 100 million terms in half a minute on my computer (printing them will take longer), and the memory usage for generating the first N terms is O(sqrt(N)).
def pair_terms() :
yield 0
# By delaying this recursion until after a yield, we avoid
# an infinite recursive loop.
generated = []
generator = pair_terms()
this = generator.next()
while True:
for j in range(len(generated)):
yield (this, generated[j])
yield (generated[j], this)
yield (this, this)
generated.append(this)
this = generator.next()
The trick is that to produce the n'th term, I only need to keep a record of terms up to sqrt(n). I do that by having the generator call itself recursively. That seems like extra work, but since you're only making O(sqrt(n)) recursive calls, the overhead of the recursive calls is a rounding error compared to generating results.
If you care more about memory than speed you can also try listing them by length, as such:
def pairTermsLength(L):
if L == 1:
yield 0
else:
for k in range(1,L//2+1):
for a in pairTermsLength(k):
if L-k != k:
for b in pairTermsLength(L-k):
yield(a,b)
yield(b,a)
else:
for b in pairTermsLength(L-k):
yield(a,b)
def pairTerms():
L = 1
while True:
for p in pairTermsLength(L):
yield p
L += 1
This will use memory and recursion depth linear to the length (in number of 0's) of the longest pair-term generated. The number of pair-terms of length n is the n-th Catalan number, which grows exponentially with n, so the memory consumption is O(log(n)). To give you an idea, for a length of 30 you are already in 10^16 territory, which is probably way more than you have time for anyway, even with a faster algorithm.

Elements of Programming Interview 5.15 (Random Subset Computation)

Algorithm problem:
Write a program which takes as input a positive integer n and size
k <= n; return a size-k subset of {0, 1, 2, .. , n -1}. The subset
should be represented as an array. All subsets should be equally
likely, and in addition, all permutations of elements of the array
should be equally likely. You may assume you have a function which
takes as input a nonnegative integer t and returns an integer in the
set {0, 1,...,t-1}.
My original solution to this in pseudocode is as follows:
Set t = n, and output the result of the random number generator into a set() until set() has size(set) == t. Return list(set)
The author solution is as follows:
def online_sampling(n, k):
changed_elements = {}
for i in range(k):
rand_idx = random.randrange(i, n)
rand_idx_mapped = changed_elements.get(rand_idx, rand_idx)
i_mapped = changed_elements.get(i, i)
changed_elements[rand_idx] = i_mapped
changed_elements[i] = rand_idx_mapped
return [changed_elements[i] for i in range(k)]
I totally understand the author's solution - my question is more about why my solution is incorrect. My guess is that it becomes greatly inefficient as t approaches n, because in that case, the probability that I need to keep running the random num function until I get a number that isn't in t gets higher and higher. If t == n, for the very last element to add to set there is just a 1/n chance that I get the correct element, and would probabilistically need to run the given rand() function n times just to get the last item.
Is this the correct reason for why my solution isn't efficient? Is there anything else I'm missing? And how would one describe the time complexity of my solution then? By the above rationale, I believe would be O(n^2) since probabilistically need to run n + n - 1 + n - 2... times.
Your solution is (almost) correct.
Firstly, it will run in O(n log n) instead of O(n^2), assuming that all operations with set are O(1). Here's why.
The expected time to add the first element to the set is 1 = n/n.
The expected time to add the second element to the set is n/(n-1), because the probability to randomly choose yet unchosen element is (n-1)/n. See geometric distribution for an explanation.
...
For k-th element, the expected time is n/(n-k). So for n elements the total time is n/n + n/(n-1) + ... + n/1 = n * (1 + 1/2 + ... + 1/n) = n log n.
Moreover, we can prove by induction that all chosen subsets will be equiprobable.
However, when you do list(set(...)), it is not guaranteed the resulting list will contain elements in the same order as you put them into a set. For example, if set is implemented as a binary search tree then the list will always be sorted. So you have to store the list of unique found elements separately.
UPD (#JimMischel): we proved the average case running time. There still is a possibility that the algorithm will run indefinitely (for example, if rand() always returns 1).
Your method has a big problem. You may return duplicate numbers if you random number generator create same number two times isn't it?
If you say set() will not keep duplicate numbers, your method has created members of set with different chance. So numbers in your set will not be equally likely.
Problem with your method is not efficiency, it does not create an equally likely result set. The author uses a variation of Fisher-Yates method for creating that subset which will be equally likely.

How can I merge two lists and sort them working in 'linear' time?

I have this, and it works:
# E. Given two lists sorted in increasing order, create and return a merged
# list of all the elements in sorted order. You may modify the passed in lists.
# Ideally, the solution should work in "linear" time, making a single
# pass of both lists.
def linear_merge(list1, list2):
finalList = []
for item in list1:
finalList.append(item)
for item in list2:
finalList.append(item)
finalList.sort()
return finalList
# +++your code here+++
return
But, I'd really like to learn this stuff well. :) What does 'linear' time mean?
Linear means O(n) in Big O notation, while your code uses a sort() which is most likely O(nlogn).
The question is asking for the standard merge algorithm. A simple Python implementation would be:
def merge(l, m):
result = []
i = j = 0
total = len(l) + len(m)
while len(result) != total:
if len(l) == i:
result += m[j:]
break
elif len(m) == j:
result += l[i:]
break
elif l[i] < m[j]:
result.append(l[i])
i += 1
else:
result.append(m[j])
j += 1
return result
>>> merge([1,2,6,7], [1,3,5,9])
[1, 1, 2, 3, 5, 6, 7, 9]
Linear time means that the time taken is bounded by some undefined constant times (in this context) the number of items in the two lists you want to merge. Your approach doesn't achieve this - it takes O(n log n) time.
When specifying how long an algorithm takes in terms of the problem size, we ignore details like how fast the machine is, which basically means we ignore all the constant terms. We use "asymptotic notation" for that. These basically describe the shape of the curve you would plot in a graph of problem size in x against time taken in y. The logic is that a bad curve (one that gets steeper quickly) will always lead to a slower execution time if the problem is big enough. It may be faster on a very small problem (depending on the constants, which probably depends on the machine) but for small problems the execution time isn't generally a big issue anyway.
The "big O" specifies an upper bound on execution time. There are related notations for average execution time and lower bounds, but "big O" is the one that gets all the attention.
O(1) is constant time - the problem size doesn't matter.
O(log n) is a quite shallow curve - the time increases a bit as the problem gets bigger.
O(n) is linear time - each unit increase means it takes a roughly constant amount of extra time. The graph is (roughly) a straight line.
O(n log n) curves upwards more steeply as the problem gets more complex, but not by very much. This is the best that a general-purpose sorting algorithm can do.
O(n squared) curves upwards a lot more steeply as the problem gets more complex. This is typical for slower sorting algorithms like bubble sort.
The nastiest algorithms are classified as "np-hard" or "np-complete" where the "np" means "non-polynomial" - the curve gets steeper quicker than any polynomial. Exponential time is bad, but some are even worse. These kinds of things are still done, but only for very small problems.
EDIT the last paragraph is wrong, as indicated by the comment. I do have some holes in my algorithm theory, and clearly it's time I checked the things I thought I had figured out. In the mean time, I'm not quite sure how to correct that paragraph, so just be warned.
For your merging problem, consider that your two input lists are already sorted. The smallest item from your output must be the smallest item from one of your inputs. Get the first item from both and compare the two, and put the smallest in your output. Put the largest back where it came from. You have done a constant amount of work and you have handled one item. Repeat until both lists are exhausted.
Some details... First, putting the item back in the list just to pull it back out again is obviously silly, but it makes the explanation easier. Next - one input list will be exhausted before the other, so you need to cope with that (basically just empty out the rest of the other list and add it to the output). Finally - you don't actually have to remove items from the input lists - again, that's just the explanation. You can just step through them.
Linear time means that the runtime of the program is proportional to the length of the input. In this case the input consists of two lists. If the lists are twice as long, then the program will run approximately twice as long. Technically, we say that the algorithm should be O(n), where n is the size of the input (in this case the length of the two input lists combined).
This appears to be homework, so I will no supply you with an answer. Even though this is not homework, I am of the opinion that you will be best served by taking a pen and a piece of paper, construct two smallish example lists which are sorted, and figure out how you would merge those two lists, by hand. Once you figured that out, implementing the algorithm is a piece of cake.
(If all goes well, you will notice that you need to iterate over each list only once, in a single direction. That means that the algorithm is indeed linear. Good luck!)
If you build the result in reverse sorted order, you can use pop() and still be O(N)
pop() from the right end of the list does not require shifting the elements, so is O(1)
Reversing the list before we return it is O(N)
>>> def merge(l, r):
... result = []
... while l and r:
... if l[-1] > r[-1]:
... result.append(l.pop())
... else:
... result.append(r.pop())
... result+=(l+r)[::-1]
... result.reverse()
... return result
...
>>> merge([1,2,6,7], [1,3,5,9])
[1, 1, 2, 3, 5, 6, 7, 9]
This thread contains various implementations of a linear-time merge algorithm. Note that for practical purposes, you would use heapq.merge.
Linear time means O(n) complexity. You can read something about algorithmn comlexity and big-O notation here: http://en.wikipedia.org/wiki/Big_O_notation .
You should try to combine those lists not after getting them in the finalList, try to merge them gradually - adding an element, assuring the result is sorted, then add next element... this should give you some ideas.
A simpler version which will require equal sized lists:
def merge_sort(L1, L2):
res = []
for i in range(len(L1)):
if(L1[i]<L2[i]):
first = L1[i]
secound = L2[i]
else:
first = L2[i]
secound = L1[i]
res.extend([first,secound])
return res
itertoolz provides an efficient implementation to merge two sorted lists
https://toolz.readthedocs.io/en/latest/_modules/toolz/itertoolz.html#merge_sorted
'Linear time' means that time is an O(n) function, where n - the number of items input (items in the lists).
f(n) = O(n) means that that there exist constants x and y such that x * n <= f(n) <= y * n.
def linear_merge(list1, list2):
finalList = []
i = 0
j = 0
while i < len(list1):
if j < len(list2):
if list1[i] < list2[j]:
finalList.append(list1[i])
i += 1
else:
finalList.append(list2[j])
j += 1
else:
finalList.append(list1[i])
i += 1
while j < len(list2):
finalList.append(list2[j])
j += 1
return finalList

Categories

Resources