Generate a sequence keeping previous element in next element python - python

I would like to generate a sequence in a list, I know how to do this using a for loop, but if I wanted to generate the list such that the previously generated element was included in the next element, how would I do this? I am very unsure
i.e generate the list such that its items were:
where x is just a symbol
[x,(x)*(x+1),(x)*(x+1)*(x+2)]
rather than [x,x+1,x+2]
Any help greatly appreciated!

I like to use a generator for this sort of thing.
def sequence(x, N):
i = 0
result = 1
while i < N:
result *= (x + i)
i += 1
yield result
>>> list(sequence(5, 10))
[5, 30, 210, 1680, 15120, 151200, 1663200, 19958400, 259459200, 3632428800L]
If you have numpy installed, this is faster:
np.multiply.accumulate(np.arange(x, x + N))

Basically, you need to maintain state between the elements, and the list comprehension won't do that for you. A couple of ways to maintain state that come to mind are, a) use a generator, b) use a class EDIT or c) a closure.
Use a generator
def product(x, n):
accumulator = 1
for i in xrange(n + 1):
accumulator *= x + i
yield accumulator
x = 5
print [n for n in product(x, 2)]
# or just list(product(x, 2))
Or, use a class to maintain state
class Accumulator(object):
def __init__(self):
self.value = 1
self.count = 0
def __call__(self, x):
self.value *= x + self.count
self.count += 1
return self.value
a = Accumulator()
x = 5
print [a(x) for _ in xrange(3)]
...The benefit of the class approach is that you could use a different value for x each iteration, like:
b = Accumulator()
print [b(x) for x in [1, 2, 3]]
>>> [1, 3, 15]
EDIT:
Just to be thorough, a closure would work, too:
def accumulator():
# we need a container here because closures keep variables by reference; could have used a list too
state = {'value': 1, 'count': 0}
def accumulate(x):
state['value'] *= x + state['count']
state['count'] += 1
return state['value']
return accumulate
a = accumulator()
print [a(5) for _ in xrange(3)]

Try this. Deque are used to get a fast access to the elements at the end of the constructing list, as well as a constant append time. pol[-1] means the last element in the queue.
from collections import deque
def multCum(f, x, end):
pol = deque([x])
for i in range(1, end):
pol.append(f(pol[-1],(x+i)))
return list(pol)
def f(x, y):
return x * y
multCum(f, 1, 3)
[1, 2, 6]

Use generators with list comprehensions.
def reqd(x, n):
'''x is the number and n is number of elements '''
yield x
i = 0
original = x
while i < n:
i += 1
x *= (original + i)
yield x
x = 2
listt = [a for a in reqd(x, 10)]
print listt
Change the list comprehension in the end to get the list that you want.

More idiomatic hacking using for-comprehensions and a custom operator:
def n(a):
global previous
previous = a
return a
[n(previous * (x+i)) for previous in [1] for i in range(0,3)]
yields, if x == 1,
[1, 2, 6]

Computationally wasteful but gets the job done
from operator import mul
z = range(5, 11)
print z
[5, 6, 7, 8, 9, 10]
[reduce(mul, z[:i]) for i in range(1, len(z))]
[5, 30, 210, 1680, 15120]

Related

How to iterate function so that output is passed as argument iteratively until condition is met

I made a function known as multiplication to multiply subsequent element in a list with each other.
def multiplication(numbers):
new_list=[]
for x in range(len(numbers)-1):
new_list.append(numbers[x+1]*numbers[x])
return new_list
Which does the following.
multiplication([1,3,5,7,8])
[3, 15, 35, 56]
multiplication([3,15,35,56])
[45, 525, 1960]
I've been trying to make a new function with a while loop to pass the output of the prior function back into itself until a single value is outputted. For example, the list above should become a single value of [24310125000]. I've tried using the below code.
def final_multiplication(numbers):
new_list=[]
while len(new_list)!=1:
for x in range(len(numbers)-1):
new_list.append(numbers[x+1]*numbers[x])
if len(new_list)==1:
return new_list
However, my attempts have resulted in crashes. Does anyone know any way to use a while loop to do this?
a simple way to write what you want to do is:
def mult(nums):
while len(nums) > 1:
nums = [n1 * n2 for n1, n2 in zip(nums, nums[1:])]
return nums
I'm sure there are cuter ways of doing it that don't involve a useless while and a walrus:
import itertools
import operator
def pairwise(iterable):
a, b = itertools.tee(iterable)
next(b, None)
yield from zip(a, b)
numbers = [1, 3, 5, 7, 8]
while len(numbers := list(itertools.starmap(operator.mul, pairwise(numbers)))) != 1:
pass
print(numbers)
Output:
[24310125000]
>>>
You could get pairs by doing something like:
from operator import mul
from functools import reduce
x = [1,3,5,7,8]
def pairs(lst):
i = 0
while True:
_slice = lst[i:i+2]
if not len(_slice) == 2:
break
yield reduce(mul, _slice)
i += 1
# Then a while loop to check the length
def reducer(lst):
while len(lst) > 1:
lst = list(pairs(lst))
return lst
reducer(x)
[24310125000]

Given an array of integers, return indices of the two numbers such that they add up to a specific target [duplicate]

I'm new to Python and have just started to try out LeetCode to build my chops. On this classic question my code misses a test case.
The problem is as follows:
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
I miss on test case [3,2,4] with the target number of 6, which should return the indices of [1,2], but hit on test case [1,5,7] with the target number of 6 (which of course returns indices [0,1]), so it appears that something is wrong in my while loop, but I'm not quite sure what.
class Solution:
def twoSum(self, nums, target):
x = 0
y = len(nums) - 1
while x < y:
if nums[x] + nums[y] == target:
return (x, y)
if nums[x] + nums[y] < target:
x += 1
else:
y -= 1
self.x = x
self.y = y
self.array = array
return None
test_case = Solution()
array = [1, 5, 7]
print(test_case.twoSum(array, 6))
Output returns null on test case [3,2,4] with target 6, so indices 1 and 2 aren't even being summarized, could I be assigning y wrong?
A brute force solution is to double nest a loop over the list where the inner loop only looks at index greater than what the outer loop is currently on.
class Solution:
def twoSum(self, nums, target):
for i, a in enumerate(nums, start=0):
for j, b in enumerate(nums[i+1:], start=0):
if a+b==target:
return [i, j+i+1]
test_case = Solution()
array = [3, 2, 4]
print(test_case.twoSum(array, 6))
array = [1, 5, 7]
print(test_case.twoSum(array, 6))
array = [2, 7, 11, 15]
print(test_case.twoSum(array, 9))
Output:
[1, 2]
[0, 1]
[0, 1]
Bit different approach. We will build a dictionary of values as we need them, which is keyed by the values we are looking for.If we look for a value we track the index of that value when it first appears. As soon as you find the values that satisfy the problem you are done. The time on this is also O(N)
class Solution:
def twoSum(self, nums, target):
look_for = {}
for n,x in enumerate(nums):
try:
return look_for[x], n
except KeyError:
look_for.setdefault(target - x,n)
test_case = Solution()
array = [1, 5, 7]
array2 = [3,2,4]
given_nums=[2,7,11,15]
print(test_case.twoSum(array, 6))
print(test_case.twoSum(array2, 6))
print(test_case.twoSum(given_nums,9))
output:
(0, 1)
(1, 2)
(0, 1)
class Solution:
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
ls=[]
l2=[]
for i in nums:
ls.append(target-i)
for i in range(len(ls)):
if ls[i] in nums :
if i!= nums.index(ls[i]):
l2.append([i,nums.index(ls[i])])
return l2[0]
x= Solution()
x.twoSum([-1,-2,-3,-4,-5],-8)
output
[2, 4]
import itertools
class Solution:
def twoSum(self, nums, target):
subsets = []
for L in range(0, len(nums)+1):
for subset in itertools.combinations(nums, L):
if len(subset)!=0:
subsets.append(subset)
print(subsets) #returns all the posible combinations as tuples, note not permutations!
#sums all the tuples
sums = [sum(tup) for tup in subsets]
indexes = []
#Checks sum of all the posible combinations
if target in sums:
i = sums.index(target)
matching_combination = subsets[i] #gets the option
for number in matching_combination:
indexes.append(nums.index(number))
return indexes
else:
return None
test_case = Solution()
array = [1,2,3]
print(test_case.twoSum(array, 4))
I was trying your example for my own learning. I am happy with what I found. I used the itertools to make all the combination of the numbers for me. Then I used list manipulation to sum all the possible combination of numbers in your input array, then I just check in one shot if the target is inside the sum array or not. If not then return None, return the indexes otherwise. Please note that this approach will return all the three indexes as well, if they add up to the target. Sorry it took so long :)
This one is more comprehensive cohesive efficient one even so shorter lines of code.
nums = [6, 7, 11, 15, 3, 6, 5, 3,99,5,4,7,2]
target = 27
n = 0
for i in range(len(nums)):
n+=1
if n == len(nums):
n == len(nums)
else:
if nums[i]+nums[n] == target:
# to find the target position
print([nums.index(nums[i]),nums.index(nums[n])])
# to get the actual numbers to add print([nums[i],nums[n]])

Python Two Sum - Brute Force Approach

I'm new to Python and have just started to try out LeetCode to build my chops. On this classic question my code misses a test case.
The problem is as follows:
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
I miss on test case [3,2,4] with the target number of 6, which should return the indices of [1,2], but hit on test case [1,5,7] with the target number of 6 (which of course returns indices [0,1]), so it appears that something is wrong in my while loop, but I'm not quite sure what.
class Solution:
def twoSum(self, nums, target):
x = 0
y = len(nums) - 1
while x < y:
if nums[x] + nums[y] == target:
return (x, y)
if nums[x] + nums[y] < target:
x += 1
else:
y -= 1
self.x = x
self.y = y
self.array = array
return None
test_case = Solution()
array = [1, 5, 7]
print(test_case.twoSum(array, 6))
Output returns null on test case [3,2,4] with target 6, so indices 1 and 2 aren't even being summarized, could I be assigning y wrong?
A brute force solution is to double nest a loop over the list where the inner loop only looks at index greater than what the outer loop is currently on.
class Solution:
def twoSum(self, nums, target):
for i, a in enumerate(nums, start=0):
for j, b in enumerate(nums[i+1:], start=0):
if a+b==target:
return [i, j+i+1]
test_case = Solution()
array = [3, 2, 4]
print(test_case.twoSum(array, 6))
array = [1, 5, 7]
print(test_case.twoSum(array, 6))
array = [2, 7, 11, 15]
print(test_case.twoSum(array, 9))
Output:
[1, 2]
[0, 1]
[0, 1]
Bit different approach. We will build a dictionary of values as we need them, which is keyed by the values we are looking for.If we look for a value we track the index of that value when it first appears. As soon as you find the values that satisfy the problem you are done. The time on this is also O(N)
class Solution:
def twoSum(self, nums, target):
look_for = {}
for n,x in enumerate(nums):
try:
return look_for[x], n
except KeyError:
look_for.setdefault(target - x,n)
test_case = Solution()
array = [1, 5, 7]
array2 = [3,2,4]
given_nums=[2,7,11,15]
print(test_case.twoSum(array, 6))
print(test_case.twoSum(array2, 6))
print(test_case.twoSum(given_nums,9))
output:
(0, 1)
(1, 2)
(0, 1)
class Solution:
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
ls=[]
l2=[]
for i in nums:
ls.append(target-i)
for i in range(len(ls)):
if ls[i] in nums :
if i!= nums.index(ls[i]):
l2.append([i,nums.index(ls[i])])
return l2[0]
x= Solution()
x.twoSum([-1,-2,-3,-4,-5],-8)
output
[2, 4]
import itertools
class Solution:
def twoSum(self, nums, target):
subsets = []
for L in range(0, len(nums)+1):
for subset in itertools.combinations(nums, L):
if len(subset)!=0:
subsets.append(subset)
print(subsets) #returns all the posible combinations as tuples, note not permutations!
#sums all the tuples
sums = [sum(tup) for tup in subsets]
indexes = []
#Checks sum of all the posible combinations
if target in sums:
i = sums.index(target)
matching_combination = subsets[i] #gets the option
for number in matching_combination:
indexes.append(nums.index(number))
return indexes
else:
return None
test_case = Solution()
array = [1,2,3]
print(test_case.twoSum(array, 4))
I was trying your example for my own learning. I am happy with what I found. I used the itertools to make all the combination of the numbers for me. Then I used list manipulation to sum all the possible combination of numbers in your input array, then I just check in one shot if the target is inside the sum array or not. If not then return None, return the indexes otherwise. Please note that this approach will return all the three indexes as well, if they add up to the target. Sorry it took so long :)
This one is more comprehensive cohesive efficient one even so shorter lines of code.
nums = [6, 7, 11, 15, 3, 6, 5, 3,99,5,4,7,2]
target = 27
n = 0
for i in range(len(nums)):
n+=1
if n == len(nums):
n == len(nums)
else:
if nums[i]+nums[n] == target:
# to find the target position
print([nums.index(nums[i]),nums.index(nums[n])])
# to get the actual numbers to add print([nums[i],nums[n]])

Efficient way to find missing elements in an integer sequence

Suppose we have two items missing in a sequence of consecutive integers and the missing elements lie between the first and last elements. I did write a code that does accomplish the task. However, I wanted to make it efficient using less loops if possible. Any help will be appreciated. Also what about the condition when we have to find more missing items (say close to n/4) instead of 2. I think then my code should be efficient right because I am breaking out from the loop earlier?
def missing_elements(L,start,end,missing_num):
complete_list = range(start,end+1)
count = 0
input_index = 0
for item in complete_list:
if item != L[input_index]:
print item
count += 1
else :
input_index += 1
if count > missing_num:
break
def main():
L = [10,11,13,14,15,16,17,18,20]
start = 10
end = 20
missing_elements(L,start,end,2)
if __name__ == "__main__":
main()
If the input sequence is sorted, you could use sets here. Take the start and end values from the input list:
def missing_elements(L):
start, end = L[0], L[-1]
return sorted(set(range(start, end + 1)).difference(L))
This assumes Python 3; for Python 2, use xrange() to avoid building a list first.
The sorted() call is optional; without it a set() is returned of the missing values, with it you get a sorted list.
Demo:
>>> L = [10,11,13,14,15,16,17,18,20]
>>> missing_elements(L)
[12, 19]
Another approach is by detecting gaps between subsequent numbers; using an older itertools library sliding window recipe:
from itertools import islice, chain
def window(seq, n=2):
"Returns a sliding window (of width n) over data from the iterable"
" s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ... "
it = iter(seq)
result = tuple(islice(it, n))
if len(result) == n:
yield result
for elem in it:
result = result[1:] + (elem,)
yield result
def missing_elements(L):
missing = chain.from_iterable(range(x + 1, y) for x, y in window(L) if (y - x) > 1)
return list(missing)
This is a pure O(n) operation, and if you know the number of missing items, you can make sure it only produces those and then stops:
def missing_elements(L, count):
missing = chain.from_iterable(range(x + 1, y) for x, y in window(L) if (y - x) > 1)
return list(islice(missing, 0, count))
This will handle larger gaps too; if you are missing 2 items at 11 and 12, it'll still work:
>>> missing_elements([10, 13, 14, 15], 2)
[11, 12]
and the above sample only had to iterate over [10, 13] to figure this out.
Assuming that L is a list of integers with no duplicates, you can infer that the part of the list between start and index is completely consecutive if and only if L[index] == L[start] + (index - start) and similarly with index and end is completely consecutive if and only if L[index] == L[end] - (end - index). This combined with splitting the list into two recursively gives a sublinear solution.
# python 3.3 and up, in older versions, replace "yield from" with yield loop
def missing_elements(L, start, end):
if end - start <= 1:
if L[end] - L[start] > 1:
yield from range(L[start] + 1, L[end])
return
index = start + (end - start) // 2
# is the lower half consecutive?
consecutive_low = L[index] == L[start] + (index - start)
if not consecutive_low:
yield from missing_elements(L, start, index)
# is the upper part consecutive?
consecutive_high = L[index] == L[end] - (end - index)
if not consecutive_high:
yield from missing_elements(L, index, end)
def main():
L = [10,11,13,14,15,16,17,18,20]
print(list(missing_elements(L,0,len(L)-1)))
L = range(10, 21)
print(list(missing_elements(L,0,len(L)-1)))
main()
missingItems = [x for x in complete_list if not x in L]
a=[1,2,3,7,5,11,20]
b=[]
def miss(a,b):
for x in range (a[0],a[-1]):
if x not in a:
b.append(x)
return b
print (miss(a,b))
ANS:[4, 6, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18, 19]
works for sorted,unsorted , with duplicates too
Using collections.Counter:
from collections import Counter
dic = Counter([10, 11, 13, 14, 15, 16, 17, 18, 20])
print([i for i in range(10, 20) if dic[i] == 0])
Output:
[12, 19]
arr = [1, 2, 5, 6, 10, 12]
diff = []
"""zip will return array of tuples (1, 2) (2, 5) (5, 6) (6, 10) (10, 12) """
for a, b in zip(arr , arr[1:]):
if a + 1 != b:
diff.extend(range(a+1, b))
print(diff)
[3, 4, 7, 8, 9, 11]
If the list is sorted we can lookup for any gap. Then generate a range object between current (+1) and next value (not inclusive) and extend it to the list of differences.
Using scipy lib:
import math
from scipy.optimize import fsolve
def mullist(a):
mul = 1
for i in a:
mul = mul*i
return mul
a = [1,2,3,4,5,6,9,10]
s = sum(a)
so = sum(range(1,11))
mulo = mullist(range(1,11))
mul = mullist(a)
over = mulo/mul
delta = so -s
# y = so - s -x
# xy = mulo/mul
def func(x):
return (so -s -x)*x-over
print int(round(fsolve(func, 0))), int(round(delta - fsolve(func, 0)))
Timing it:
$ python -mtimeit -s "$(cat with_scipy.py)"
7 8
100000000 loops, best of 3: 0.0181 usec per loop
Other option is:
>>> from sets import Set
>>> a = Set(range(1,11))
>>> b = Set([1,2,3,4,5,6,9,10])
>>> a-b
Set([8, 7])
And the timing is:
Set([8, 7])
100000000 loops, best of 3: 0.0178 usec per loop
My take was to use no loops and set operations:
def find_missing(in_list):
complete_set = set(range(in_list[0], in_list[-1] + 1))
return complete_set - set(in_list)
def main():
sample = [10, 11, 13, 14, 15, 16, 17, 18, 20]
print find_missing(sample)
if __name__ == "__main__":
main()
# => set([19, 12])
Simply walk the list and look for non-consecutive numbers:
prev = L[0]
for this in L[1:]:
if this > prev+1:
for item in range(prev+1, this): # this handles gaps of 1 or more
print item
prev = this
Here's a one-liner:
In [10]: l = [10,11,13,14,15,16,17,18,20]
In [11]: [i for i, (n1, n2) in enumerate(zip(l[:-1], l[1:])) if n1 + 1 != n2]
Out[11]: [1, 7]
I use the list, slicing to offset the copies by one, and use enumerate to get the indices of the missing item.
For long lists, this isn't great because it's not O(log(n)), but I think it should be pretty efficient versus using a set for small inputs. izip from itertools would probably make it quicker still.
>>> l = [10,11,13,14,15,16,17,18,20]
>>> [l[i]+1 for i, j in enumerate(l) if (l+[0])[i+1] - l[i] > 1]
[12, 19]
We found a missing value if the difference between two consecutive numbers is greater than 1:
>>> L = [10,11,13,14,15,16,17,18,20]
>>> [x + 1 for x, y in zip(L[:-1], L[1:]) if y - x > 1]
[12, 19]
Note: Python 3. In Python 2 use itertools.izip.
Improved version for more than one value missing in a row:
>>> import itertools as it
>>> L = [10,11,14,15,16,17,18,20] # 12, 13 and 19 missing
>>> [x + diff for x, y in zip(it.islice(L, None, len(L) - 1),
it.islice(L, 1, None))
for diff in range(1, y - x) if diff]
[12, 13, 19]
def missing_elements(inlist):
if len(inlist) <= 1:
return []
else:
if inlist[1]-inlist[0] > 1:
return [inlist[0]+1] + missing_elements([inlist[0]+1] + inlist[1:])
else:
return missing_elements(inlist[1:])
First we should sort the list and then we check for each element, except the last one, if the next value is in the list. Be carefull not to have duplicates in the list!
l.sort()
[l[i]+1 for i in range(len(l)-1) if l[i]+1 not in l]
I stumbled on this looking for a different kind of efficiency -- given a list of unique serial numbers, possibly very sparse, yield the next available serial number, without creating the entire set in memory. (Think of an inventory where items come and go frequently, but some are long-lived.)
def get_serial(string_ids, longtail=False):
int_list = map(int, string_ids)
int_list.sort()
n = len(int_list)
for i in range(0, n-1):
nextserial = int_list[i]+1
while nextserial < int_list[i+1]:
yield nextserial
nextserial+=1
while longtail:
nextserial+=1
yield nextserial
[...]
def main():
[...]
serialgenerator = get_serial(list1, longtail=True)
while somecondition:
newserial = next(serialgenerator)
(Input is a list of string representations of integers, yield is an integer, so not completely generic code. longtail provides extrapolation if we run out of range.)
There's also an answer to a similar question which suggests using a bitarray for efficiently handling a large sequence of integers.
Some versions of my code used functions from itertools but I ended up abandoning that approach.
A bit of mathematics and we get a simple solution. The below solution works for integers from m to n.
Works for both sorted and unsorted postive and negative numbers.
#numbers = [-1,-2,0,1,2,3,5]
numbers = [-2,0,1,2,5,-1,3]
sum_of_nums = 0
max = numbers[0]
min = numbers[0]
for i in numbers:
if i > max:
max = i
if i < min:
min = i
sum_of_nums += i
# Total : sum of numbers from m to n
total = ((max - min + 1) * (max + min)) / 2
# Subtract total with sum of numbers which will give the missing value
print total - sum_of_nums
With this code you can find any missing values in a sequence, except the last number. It in only required to input your data into excel file with column name "numbers".
import pandas as pd
import numpy as np
data = pd.read_excel("numbers.xlsx")
data_sort=data.sort_values('numbers',ascending=True)
index=list(range(len(data_sort)))
data_sort['index']=index
data_sort['index']=data_sort['index']+1
missing=[]
for i in range (len(data_sort)-1):
if data_sort['numbers'].iloc[i+1]-data_sort['numbers'].iloc[i]>1:
gap=data_sort['numbers'].iloc[i+1]-data_sort['numbers'].iloc[i]
numerator=1
for j in range (1,gap):
mis_value=data_sort['numbers'].iloc[i+1]-numerator
missing.append(mis_value)
numerator=numerator+1
print(np.sort(missing))

My implementation of merging two sorted lists in linear time - what could be improved?

Fromg Google's Python Class:
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.
Here's my solution:
def linear_merge(list1, list2):
merged_list = []
i = 0
j = 0
while True:
if i == len(list1):
return merged_list + list2[j:]
if j == len(list2):
return merged_list + list1[i:]
if list1[i] <= list2[j]:
merged_list.append(list1[i])
i += 1
else:
merged_list.append(list2[j])
j += 1
First of all, is it okay to use an infinite loop here? Should I break out of the loop using the break keyword when I'm done merging the list, or are the returns okay here?
I've seen similar questions asked here, and all the solutions look quite similar to mine, i.e. very C-like. Is there no more python-like solution? Or is this because of the nature of the algorithm?
This question covers this in more detail than you probably need. ;) The chosen answer matches your requirement. If I needed to do this myself, I would do it in the way that dbr described in his or her answer (add the lists together, sort the new list) as it is very simple.
EDIT:
I'm adding an implementation below. I actually saw this in another answer here which seems to have been deleted. I'm just hoping it wasn't deleted because it had an error which I'm not catching. ;)
def mergeSortedLists(a, b):
l = []
while a and b:
if a[0] < b[0]:
l.append(a.pop(0))
else:
l.append(b.pop(0))
return l + a + b
Here's a generator approach. You've probably noticed that a whole lot of these "generate lists" can be done well as generator functions. They're very useful: they don't require you to generate the whole list before using data from it, to keep the whole list in memory, and you can use them to directly generate many data types, not just lists.
This works if passed any iterator, not just lists.
This approach also passes one of the more useful tests: it behaves well when passed an infinite or near-infinite iterator, eg. linear_merge(xrange(10**9), xrange(10**9)).
The redundancy in the two cases could probably be reduced, which would be useful if you wanted to support merging more than two lists, but for clarity I didn't do that here.
def linear_merge(list1, list2):
"""
>>> a = [1, 3, 5, 7]
>>> b = [2, 4, 6, 8]
>>> [i for i in linear_merge(a, b)]
[1, 2, 3, 4, 5, 6, 7, 8]
>>> [i for i in linear_merge(b, a)]
[1, 2, 3, 4, 5, 6, 7, 8]
>>> a = [1, 2, 2, 3]
>>> b = [2, 2, 4, 4]
>>> [i for i in linear_merge(a, b)]
[1, 2, 2, 2, 2, 3, 4, 4]
"""
list1 = iter(list1)
list2 = iter(list2)
value1 = next(list1)
value2 = next(list2)
# We'll normally exit this loop from a next() call raising StopIteration, which is
# how a generator function exits anyway.
while True:
if value1 <= value2:
# Yield the lower value.
yield value1
try:
# Grab the next value from list1.
value1 = next(list1)
except StopIteration:
# list1 is empty. Yield the last value we received from list2, then
# yield the rest of list2.
yield value2
while True:
yield next(list2)
else:
yield value2
try:
value2 = next(list2)
except StopIteration:
# list2 is empty.
yield value1
while True:
yield next(list1)
Why stop at two lists?
Here's my generator based implementation to merge any number of sorted iterators in linear time.
I'm not sure why something like this isn't in itertools...
def merge(*sortedlists):
# Create a list of tuples containing each iterator and its first value
iterlist = [[i,i.next()] for i in [iter(j) for j in sortedlists]]
# Perform an initial sort of each iterator's first value
iterlist.sort(key=lambda x: x[1])
# Helper function to move the larger first item to its proper position
def reorder(iterlist, i):
if i == len(iterlist) or iterlist[0][1] < iterlist[i][1]:
iterlist.insert(i-1,iterlist.pop(0))
else:
reorder(iterlist,i+1)
while True:
if len(iterlist):
# Reorder the list if the 1st element has grown larger than the 2nd
if len(iterlist) > 1 and iterlist[0][1] > iterlist[1][1]:
reorder(iterlist, 1)
yield iterlist[0][1]
# try to pull the next value from the current iterator
try:
iterlist[0][1] = iterlist[0][0].next()
except StopIteration:
del iterlist[0]
else:
break
Here's an example:
x = [1,10,20,33,99]
y = [3,11,20,99,1001]
z = [3,5,7,70,1002]
[i for i in merge(x,y,z)]
hi i just did this exercise and i was wondering why not use,
def linear_merge(list1, list2):
return sorted(list1 + list2)
pythons sorted function is linear isn't it?
Here's my implementation from a previous question:
def merge(*args):
import copy
def merge_lists(left, right):
result = []
while (len(left) and len(right)):
which_list = (left if left[0] <= right[0] else right)
result.append(which_list.pop(0))
return result + left + right
lists = [arg for arg in args]
while len(lists) > 1:
left, right = copy.copy(lists.pop(0)), copy.copy(lists.pop(0))
result = merge_lists(left, right)
lists.append(result)
return lists.pop(0)
Another generator:
def merge(xs, ys):
xs = iter(xs)
ys = iter(ys)
try:
y = next(ys)
except StopIteration:
for x in xs:
yield x
raise StopIteration
while True:
for x in xs:
if x > y:
yield y
break
yield x
else:
yield y
for y in ys:
yield y
break
xs, ys, y = ys, xs, x
I agree with other answers that extending and sorting is the most straightforward way, but if you must merge, this will be a little faster because it does not make two calls to len every iteration nor does it do a bounds check. The Python pattern, if you could call it that, is to avoid testing for a rare case and catch the exception instead.
def linear_merge(list1, list2):
merged_list = []
i = 0
j = 0
try:
while True:
if list1[i] <= list2[j]:
merged_list.append(list1[i])
i += 1
else:
merged_list.append(list2[j])
j += 1
except IndexError:
if i == len(list1):
merged_list.extend(list2[j:])
if j == len(list2):
merged_list.extend(list1[i:])
return merged_list
edit
Optimized per John Machin's comment. Moved try outside of while True and extended merged_list upon exception.
According to a note here:
# Note: the solution above is kind of cute, but unforunately list.pop(0)
# is not constant time with the standard python list implementation, so
# the above is not strictly linear time.
# An alternate approach uses pop(-1) to remove the endmost elements
# from each list, building a solution list which is backwards.
# Then use reversed() to put the result back in the correct order. That
# solution works in linear time, but is more ugly.
and this link http://www.ics.uci.edu/~pattis/ICS-33/lectures/complexitypython.txt
append is O(1), reverse is O(n) but then it also says that pop is O(n) so which is which? Anyway I have modified the accepted answer to use pop(-1):
def linear_merge(list1, list2):
# +++your code here+++
ret = []
while list1 and list2:
if list1[-1] > list2[-1]:
ret.append(list1.pop(-1))
else:
ret.append(list2.pop(-1))
ret.reverse()
return list1 + list2 + ret
This solution runs in linear time and without editing l1 and l2:
def merge(l1, l2):
m, m2 = len(l1), len(l2)
newList = []
l, r = 0, 0
while l < m and r < m2:
if l1[l] < l2[r]:
newList.append(l1[l])
l += 1
else:
newList.append(l2[r])
r += 1
return newList + l1[l:] + l2[r:]

Categories

Resources