Given list a = [1, 2, 2, 3] and its sublist b = [1, 2] find a list complementing b in such a way that sorted(a) == sorted(b + complement). In the example above the complement would be a list of [2, 3].
It is tempting to use list comprehension:
complement = [x for x in a if x not in b]
or sets:
complement = list(set(a) - set(b))
However, both of this ways will return complement = [3].
An obvious way of doing it would be:
complement = a[:]
for element in b:
complement.remove(element)
But that feels deeply unsatisfying and not very Pythonic. Am I missing an obvious idiom or is this the way?
As pointed out below what about performance this is O(n^2) Is there more efficient way?
The only more declarative and thus Pythonic way that pops into my mind and that improves performance for large b (and a) is to use some sort of counter with decrement:
from collections import Counter
class DecrementCounter(Counter):
def decrement(self,x):
if self[x]:
self[x] -= 1
return True
return False
Now we can use list comprehension:
b_count = DecrementCounter(b)
complement = [x for x in a if not b_count.decrement(x)]
Here we thus keep track of the counts in b, for each element in a we look whether it is part of b_count. If that is indeed the case we decrement the counter and ignore the element. Otherwise we add it to the complement. Note that this only works, if we are sure such complement exists.
After you have constructed the complement, you can check if the complement exists with:
not bool(+b_count)
If this is False, then such complement cannot be constructed (for instance a=[1] and b=[1,3]). So a full implementation could be:
b_count = DecrementCounter(b)
complement = [x for x in a if not b_count.decrement(x)]
if +b_count:
raise ValueError('complement cannot be constructed')
If dictionary lookup runs in O(1) (which it usually does, only in rare occasions it is O(n)), then this algorithm runs in O(|a|+|b|) (so the sum of the sizes of the lists). Whereas the remove approach will usually run in O(|a|×|b|).
In order to reduce complexity to your already valid approach, you could use collections.Counter (which is a specialized dictionary with fast lookup) to count items in both lists.
Then update the count by substracting values, and in the end filter the list by only keeping items whose count is > 0 and rebuild it/chain it using itertools.chain
from collections import Counter
import itertools
a = [1, 2, 2, 2, 3]
b = [1, 2]
print(list(itertools.chain.from_iterable(x*[k] for k,x in (Counter(a)-Counter(b)).items() if x > 0)))
result:
[2, 2, 3]
O(n log n)
a = [1, 2, 2, 3]
b = [1, 2]
a.sort()
b.sort()
L = []
i = j = 0
while i < len(a) and j < len(b):
if a[i] < b[j]:
L.append(a[i])
i += 1
elif a[i] > b[j]:
L.append(b[j])
j += 1
else:
i += 1
j += 1
while i < len(a):
L.append(a[i])
i += 1
while j < len(b):
L.append(b[j])
j += 1
print(L)
If the order of elements in the complement doesn't matter, then collections.Counter is all that is needed:
from collections import Counter
a = [1, 2, 3, 2]
b = [1, 2]
complement = list((Counter(a) - Counter(b)).elements()) # complement = [2, 3]
If the order of items in the complement should be the same order as in the original list, then use something like this:
from collections import Counter, defaultdict
from itertools import count
a = [1,2,3,2]
b = [2,1]
c = Counter(b)
d = defaultdict(count)
complement = [x for x in a if next(d[x]) >= c[x]] # complement = [3, 2]
Main idea: if the values are not unique, make them unique
def add_duplicate_position(items):
element_counter = {}
for item in items:
element_counter[item] = element_counter.setdefault(item,-1) + 1
yield element_counter[item], item
assert list(add_duplicate_position([1, 2, 2, 3])) == [(0, 1), (0, 2), (1, 2), (0, 3)]
def create_complementary_list_with_duplicates(a,b):
a = list(add_duplicate_position(a))
b = set(add_duplicate_position(b))
return [item for _,item in [x for x in a if x not in b]]
a = [1, 2, 2, 3]
b = [1, 2]
assert create_complementary_list_with_duplicates(a,b) == [2, 3]
Related
I'm stuck with comparing two lists and finding how many elements are <= list1 (list2 <= list1) and putting the result in a new_list.
Here are the test cases:
Test Case 1:
list1 = [1, 4, 2, 4]
list2 = [3, 5]
Output: [2, 4]
Explanation:
For list2[0] = 3, we have 2 elements in list1
(list1[0] = 1 and list1[2] = 2) that are <= list2[0].
For list2[1] = 5, we have 4 elements in list1
(list1[0] = 1, list1[1] = 4, list1[2] = 2, and list1[3] = 4)
that are <= list2[1]
Test Case 2:
list1 = [1, 2, 3]
list2 = [2, 4]
Output: [2, 3]
Explanation:
For list2[0] = 2, we have 2 elements in list1
(list1[0] = 1 and list1[1] = 2) that are <= list2[0].
For list2[1] = 4, we have 3 elements in list1
(list1[0] = 1, list1[1] = 2, list1[2] = 3)
that are <= list2[1]
I was thinking to solve with two pointers where I place the first pointer in list1[0] and 2nd pointer in list2[0] then compare each element in both lists and increment a count each time I see an element in list2 is smaller than elements in list1. The implementation is a little challenging to me and I'm sure whether it's working or not.
Here what I wrote:
def counts(list1, list2):
new_list = []
count = 0
a = list1[0]
b = list2[0]
i, j = 1, 1
while a or b:
if a <= b:
count += 1
new_list.append(a[i])
i += 1
return new_list
You can use Python sum on boolean to get the number of items that match, e.g.
[sum(l1x <= l2x for l1x in list1) for l2x in list2]
If you need efficiency1, you can switch to a numpy based solution, such as
np.sum(np.asarray(list1)[:, None] <= [list2], axis=0)
If you want the version without list comprehension or without sum:
res = []
for l2x in list2:
cnt = 0
for l1x in list1:
cnt += l1x <= l2x
res.append(cnt)
1 Disclaimer: I did not test the actual efficiency.
The easiest way is:
import numpy as np
[(np.array(list1)<=l2).sum() for l2 in list2]
But if you need a more efficient solution, just let me know
You could sort both the input lists. Then binary search(bisect.bisect_right from std lib) to get the insertion position of values in list2 in list1. The position would eventually tell you how many values are less than the value of the items in list2.
from bisect import bisect_right # For binary search.
srt_l1 = sorted(list1) # NlogN
srt_l2 = sorted(list2) # MlogM
out = [bisect_right(srt_l1, val) for val in srt_l2] # MlogN
This brings the time complexity to O(NlogN + MlogM) with extra space of N+M(though you could sort in-place using list.sort). The suggested answers above have a time complexity of O(N*M).
Say I have 2 lists
a = [1, 2, 3, 4, 7, 1]
b = [1, 2, 4, 5, 7, 1]
I want it to make a third list with only the elements that are equal in the same index in each list, in this case
c = [1, 2, 7, 1]
Is there a simple way to do this?
simplest method would probably just be doing a list comprehension:
c = [x for x,y in zip(a,b) if x == y]
for the sake of a different approach and just out of interest..could also do it this way:
from itertools import compress
mask = [x==y for x,y in zip(a,b)]
c = list(compress(a,mask))
or if numpy is an option (good for larger lists):
import numpy as np
a,b = np.array(a), np.array(b)
c = a[np.equal(a,b)].tolist()
all ways give:
[1, 2, 7, 1]
You can zip the lists and return the test (which will return a variable of type boolean):
[i == j for i, j in zip(x_list, y_list)]
You could use any to quickly check for the existence of a False (it will be false when the two items are not the same) if you don't need the values:
any(i != j for i, j in zip(x_list, y_list))
The any version would break once a False is found meaning you may not have to traverse the whole lists except in the worst case.
If both lists are of equal length,c = [val for ind, val in enumerate(a) if b[ind] == val] should work.
I am trying to create a function that receives a list and return another list with the repeated elements.
For example for the input A = [2,2,1,1,3,2] (the list is not sorted) and the function would return result = [[1,1], [2,2,2]]. The result doesn't need to be sorted.
I already did it in Wolfram Mathematica but now I have to translate it to python3, Mathematica has some functions like Select, Map and Split that makes it very simple without using long loops with a lot of instructions.
result = [[x] * A.count(x) for x in set(A) if A.count(x) > 1]
Simple approach:
def grpBySameConsecutiveItem(l):
rv= []
last = None
for elem in l:
if last == None:
last = [elem]
continue
if elem == last[0]:
last.append(elem)
continue
if len(last) > 1:
rv.append(last)
last = [elem]
return rv
print grpBySameConsecutiveItem([1,2,1,1,1,2,2,3,4,4,4,4,5,4])
Output:
[[1, 1, 1], [2, 2], [4, 4, 4, 4]]
You can sort your output afterwards if you want to have it sorted or sort your inputlist , then you wouldnt get consecutive identical numbers any longer though.
See this https://stackoverflow.com/a/4174955/7505395 for how to sort lists of lists depending on an index (just use 0) as all your inner lists are identical.
You could also use itertools - it hast things like TakeWhile - that looks much smarter if used
This will ignore consecutive ones, and just collect them all:
def grpByValue(lis):
d = {}
for key in lis:
if key in d:
d[key] += 1
else:
d[key] = 1
print(d)
rv = []
for k in d:
if (d[k]<2):
continue
rv.append([])
for n in range(0,d[k]):
rv[-1].append(k)
return rv
data = [1,2,1,1,1,2,2,3,4,4,4,4,5,4]
print grpByValue(data)
Output:
[[1, 1, 1, 1], [2, 2, 2], [4, 4, 4, 4, 4]]
You could do this with a list comprehension:
A = [1,1,1,2,2,3,3,3]
B = []
[B.append([n]*A.count(n)) for n in A if B.count([n]*A.count(n)) == 0]
outputs [[1,1,1],[2,2],[3,3,3]]
Or more pythonically:
A = [1,2,2,3,4,1,1,2,2,2,3,3,4,4,4]
B = []
for n in A:
if B.count([n]*A.count(n)) == 0:
B.append([n]*A.count(n))
outputs [[1,1,1],[2,2,2,2,2],[3,3,3],[4,4,4,4]]
Works with sorted or unsorted list, if you need to sort the list before hand you can do for n in sorted(A)
This is a job for Counter(). Iterating over each element, x, and checking A.count(x) has a O(N^2) complexity. Counter() will count how many times each element exists in your iterable in one pass and then you can generate your result by iterating over that dictionary.
>>> from collections import Counter
>>> A = [2,2,1,1,3,2]
>>> counts = Counter(A)
>>> result = [[key] * value for key, value in counts.items() if value > 1]
>>> result
[[2, 2, 2], [[1, 1]]
I want to write a small code in python that Swap Elements in a list this program will accept a list, and will return a list that exchanges the positions of each pair of adjacent elements: positions 0 and 1, positions 2 and 3, and so on. If the list has an odd number of elements, then the element in the last position stays “in place”.
Before: [1,2,3,4,5]
After: [2,1,4,3,5]
This looks unpythonic. What is the Python way to do it?
Here is a neat one, if you are always guaranteed to have even numbers:
nums = [1,2,3,4,5,6]
print([nums[i^1] for i in range(len(nums))])
>>[2, 1, 4, 3, 6, 5]
Explanation:
print (0^1) #1
print (1^1) #0
print (2^1) #3
print (3^1) #2
print (4^1) #5
print (5^1) #4
As a refresher, the XOR has the following effect:
A B | Output
---------------
0 0 0
0 1 1
1 0 1
1 1 0
And the official description: Each bit of the output is the same as the corresponding bit in x if that bit in y is 0, and it's the complement of the bit in x if that bit in y is 1.
Most pythonic way:
def swappairwise(a):
l = len(a)&~1
a[1:l:2],a[:l:2] = a[:l:2],a[1:l:2]
Building on the answer above from #Arpegius, here a, hopefully, somewhat more readable solution. Uses the same approach.
def swap_list_pairwise(lis):
"""Pairwise swap of all elements in a list.
If the number of elements is odd, the leftover element
stays at its place.
"""
length = len(lis)
# Stop at second last if length is odd, otherwise use full list.
end = length - 1 if length % 2 else length
lis[1:end:2], lis[:end:2] = lis[:end:2], lis[1:end:2]
If you want "pythonic", try "How do you split a list into evenly sized chunks in Python?", followed by a map() that reverses every chunk. May not be too performant, though.
(Oh, forgot the flattening of the list at the end)
Here is a way:
def pairwise_swap(iterable):
for i, value in enumerate(iterable):
if i % 2 == 0:
saved = value
else:
yield value
yield saved
>>> list(pairwise_swap(range(10)))
[1, 0, 3, 2, 5, 4, 7, 6, 9, 8]
Nice approach by #Alok above. This should fix the missing last
element if the number of elements is odd.
def pairwise_swap(iterable):
"""Pairwise swap of all elements in an iterable.
If the number of elements is odd, the leftover element
stays at its place.
"""
for i, value in enumerate(iterable):
if i % 2 == 0:
saved = value
else:
yield value
yield saved
# Don't forget the last element if `iterable` has an odd
# number of elements. Since counting starts a zero, we need
# to test if `i` is even.
if iterable and i % 2 == 0:
yield value
How about trying this one out?
>>> l = [1,2,3,4,5,6]
>>> pl = [l[i:i+2] for i in range(0,len(l),2)]
>>> pl
[[1, 2], [3, 4], [5, 6]]
>>> for i in pl:
... i[0],i[1] = i[1],i[0]
... print i
...
[2, 1]
[4, 3]
[6, 5]
>>> pl
[[2, 1], [4, 3], [6, 5]]
>>>
>>> zpl = [i for sublist in pl for i in sublist]
>>> zpl
[2, 1, 4, 3, 6, 5]
>>>
I tried to resolve in the easiest way out.
It will work for both even and odd elements. if list elements are even
first part will work if not else will do his task.
a = list(input("Put your list here: "))
len_a = len(a)
last_element = len_a-1
result = 0
if len_a % 2==0:
for i in range(0, len(a), 2):
a[i], a[i + 1] = a[i + 1], a[i]
print("its if",a)
else:
a_n = a.pop()
for i in range(0, len(a), 2):
a[i], a[i + 1] = a[i + 1], a[i]
a.insert(0,a_n)
# a.insert(last_element,a_n) if you want last element remain unmodified
print("its else:",a)
This is what worked for me. Hopefully, it can help others. I think about using boxes. The value in the first position needs a temporary place to be before it can be swapped, so I assign it to x and then make the swap. Incrementing by 2 will skip over the last element, so it's okay with odd-numbered lists as well.
a = [int(s) for s in input().split()]
i = 0
while i in range(0, len(a) - 1):
x = a[i]
a[i] = a[i + 1]
a[i + 1] = x
i += 2
print(a)
Found this to be much simpler solution and handels the list with odd elements
elements = list(map(int,input().split()))
swap = []
i = 0
while i < len(elements):
if i+1 < len(elements):
swap.append(elements[i+1])
swap.append(elements[i])
else:
swap.append(elements[-1])
i = i+2
print(swap)
I'm supposed to create a function, which input is a list and two numbers, the function reverses the sublist which its place is indicated by the two numbers.
for example this is what it's supposed to do:
>>> lst = [1, 2, 3, 4, 5]
>>> reverse_sublist (lst,0,4)
>>> lst [4, 3, 2, 1, 5]
I created a function and it works, but I'm not sure is it's in place.
This is my code:
def reverse_sublist(lst,start,end):
sublist=lst[start:end]
sublist.reverse()
lst[start:end]=sublist
print(lst)
def reverse_sublist(lst,start,end):
lst[start:end] = lst[start:end][::-1]
return lst
Partial reverse with no temporary list (replace range with xrange if you use Python 2):
def partial_reverse(list_, from_, to):
for i in range(0, int((to - from_)/2)):
(list_[from_+i], list_[to-i]) = (list_[to-i], list_[from_+i])
list_ = [1, 2, 3, 4, 5, 6, 7, 8]
partial_reverse(list_, 3, 7)
print(list_)
Easiest way to reverse a list in a partial or complete manner.
listVar = ['a','b','c','d']
def listReverse(list,start,end):
while(start<end):
temp = list[start]
list[start] = list[end] #Swaping
list[end]=temp
start+=1
end-=1
print(list)
listReverse(listVar,1,3)
Output : - ['a', 'd', 'c', 'b']
Not sure if you have a similar problem as mine, but i needed to reverse a list in place.
The only piece I was missing was [:]
exStr = "String"
def change(var):
var[:] = var[::-1] # This line here
print(exStr) #"String"
change(exStr)
print(exStr) #"gnirtS"
... I'm not sure is it's in place.
...
lst[start:end]=sublist
Yes, it's in place. lst is never rebound, only its object mutated.
Just use a slice:
>>> lst = [1, 2, 3, 4, 5]
>>> lst[0:len(lst[3::-1])]=lst[3::-1]
>>> lst
[4, 3, 2, 1, 5]
Or, perhaps easier to understand:
>>> lst = [1, 2, 3, 4, 5]
>>> sl=lst[3::-1]
>>> lst[0:len(sl)]=sl
>>> lst
[4, 3, 2, 1, 5]
lst[::-1] is the idiomatic way to reverse a list in Python, The following show how and that it was in-place:
>>> lst = [1, 2, 3, 4, 5]
>>> id(lst)
12229328
>>> lst[:] = lst[::-1]
>>> lst
[5, 4, 3, 2, 1]
>>> id(lst)
12229328
Try some crazy slicing, see Explain Python's slice notation and http://docs.python.org/2.3/whatsnew/section-slices.html
x = [1,2,3,4,5,6,7,8]
def sublist_reverse(start_rev, end_rev, lst):
return lst[:end_rev-1:start_rev-1]+lst[:[end_rev]
print sublist_reverse(0,4,x)
[out]:
[8, 7, 6, 5, 4, 3, 2, 1]
I have two ways for in-place reversal, the simple way is to loop through the list half-way, swapping the elements with the respective mirror-elements. By mirror-element I mean (first, last), (2nd, 2nd-last), (3rd, 3rd-last), etc.
def reverse_list(A):
for i in range(len(A) // 2): # half-way
A[i], A[len(A) - i - 1] = A[len(A) - i - 1], A[i] #swap
return A
The other way is similar to the above but using recursion as opposed to a "loop":
def reverse_list(A):
def rev(A, start, stop):
A[start], A[stop] = A[stop], A[start] # swap
if stop - start > 1: # until halfway
rev(A, start + 1, stop - 1)
return A
return rev(A, 0, len(A) - 1)
I've conducted a tiny experiment and it seems that any assignment to list slice causes memory allocation:
import resource
resource.setrlimit(resource.RLIMIT_AS, (64 * 1024, 64 * 1024))
try:
# Python 2
zrange = xrange
arr_size = 3 * 1024
except NameError:
# Python 3
zrange = range
arr_size = 4 * 1024
arr = list(zrange(arr_size))
# We could allocate additional 100 integers, so there should be enough free memory
# to allocate a couple of variables for indexes in the statement below
# additional_memory = list(zrange(100))
# MemoryError is raised here
arr[:] = zrange(arr_size)
So you have to use for loop to reverse a sublist in place.
PS: If you want to repeat this test, you should ensure that setrlimit RLIMIT_AS works fine on your platform. Also arr_size may vary for different python implementations.
Two methods in-place and constant memory:
def reverse_swap(arr, start=None, end=None):
"""
Swap two edge pointers until meeting in the center.
"""
if start is None:
start = 0
if end is None:
end = len(arr)
i = start
j = end - 1
while i < j:
arr[i], arr[j] = arr[j], arr[i]
i += 1
j -= 1
def reverse_slice(arr, start=None, end=None):
"""
Use python slice assignment but use a generator on the right-hand-side
instead of slice notation to prevent allocating another list.
"""
if start is None:
start = 0
if end is None:
end = len(arr)
arr[start:end] = (arr[i] for i in range(end - 1, start - 1, -1))
The simplest way is probably to use slice assignment and reversed():
lst[start:end] = reversed(lst[start:end])
You could also slice and reverse at the same time, however this generally requires either using negative indexes or specially handling the case when start = 0, i.e.:
lst[start:end] = lst[end-len(lst)-1:start-len(lst)-1:-1]
or
lst[start:end] = lst[end-1::-1] if start == 0 else lst[end-1:start-1:-1]
OTOH, using slicing alone is faster than the reversed() solution.
Much cleaner way to do this
a = [1,2,3,4,5,6,7,8,9]
i = 2
j = 7
while (i<j):
a[i],a[j]=a[j],a[i]
j -= 1
i += 1
print(a)
lst = [1,2,3,4,5,6,7,8]
Suppose you have to reverse 2nd position to 4th position in place.
lst[2:5] = lst[2:5][::-1]
Output:
[1,2,5,4,3,6,7,8]