Related
Say, I have a list and a sub-list constructed from that list. I want to find the number from the sub-list that appears earliest in the original list.
Example:
lst = [5, 3, 4, 1, 2, 6]
sublst = [1, 2, 3]
In this case, I want to select 3 since it appears in lst 2nd, while 1 and 2 appear 4th and 5th respectively. What I have so far:
lst[min(lst.index(num) for num in sublst)]
This seems really convoluted and difficult-to-read. I was wondering if there was a cleaner way to write this.
You should make sublst a set to make it more efficient to search in it. Then you could use a simple for loop:
lst = [5, 3, 4, 1, 2, 6]
sublst = set([1, 2, 3])
for l in lst:
if l in sublst:
break
print(l)
You could also write that as a generator comprehension, finding all values in lst that are in sublst. By using a generator we will stop at the first matching value:
first = (l for l in lst if l in sublst)
print(next(first))
Output in both cases for your sample data is
3
I have a sorted list with duplicate elements like
>>> randList = [1, 2, 2, 3, 4, 4, 5]
>>> randList
[1, 2, 2, 3, 4, 4, 5]
I need to create a list that removes the adjacent duplicate elements. I can do it like:
>>>> dupList = []
for num in nums:
if num not in dupList:
dupList.append(num)
But I want to do it with list comprehension. I tried the following code:
>>> newList = []
>>> newList = [num for num in randList if num not in newList]
But I get the result like the if condition isn't working.
>>> newList
[1, 2, 2, 3, 4, 4, 5]
Any help would be appreciated.
Thanks!!
Edit 1: The wording of the question does seem to be confusing given the data I have provided. The for loop that I am using will remove all duplicates but since I am sorting the list beforehand, that shouldn't a problem when removing adjacent duplicates.
Using itertools.groupby is the simplest approach to remove adjacent (and only adjacent) duplicates, even for unsorted input:
>>> from itertools import groupby
>>> [k for k, _ in groupby(randList)]
[1, 2, 3, 4, 5]
Removing all duplicates while maintaining the order of occurence can be efficiently achieved with an OrderedDict. This, as well, works for ordered and unordered input:
>>> from collections import OrderedDict
>>> list(OrderedDict.fromkeys(randList))
[1, 2, 3, 4, 5]
I need to create a list that removes the adjacent duplicate elements
Note that your for loop based solution will remove ALL duplicates, not only adjacent ones. Test it with this:
rand_list = [1, 2, 2, 3, 4, 4, 2, 5, 1]
according to your spec the result should be:
[1, 2, 3, 4, 2, 5, 1]
but you'll get
[1, 2, 3, 4, 5]
instead.
A working solution to only remove adjacent duplicates is to use a generator:
def dedup_adjacent(seq):
prev = seq[0]
yield prev
for current in seq[1:]:
if current == prev:
continue
yield current
prev = current
rand_list = [1, 2, 2, 3, 4, 4, 2, 5, 1]
list(dedup_adjacent(rand_list))
=> [1, 2, 3, 4, 2, 5, 1]
Python first evaluates the list comprehension and then assigns it to newList, so you cannot refer to it during execution of the list comprehension.
You can remove dublicates in two ways:-
1. Using for loop
rand_list = [1,2,2,3,3,4,5]
new_list=[]
for i in rand_list:
if i not in new_list:
new_list.append(i)
Convert list to set,then again convert set to list,and at last sort the new list.
Since set stores values in any order so when we convert set into list you need to sort the list so that you get the item in ascending order
rand_list = [1,2,2,3,3,4,5]
sets = set(rand_list)
new_list = list(sets)
new_list.sort()
Update: Comparison of different Approaches
There have been three ways of achieving the goal of removing adjacent duplicate elements in a sorted list, i.e. removing all duplicates:
using groupby (only adjacent elements, requires initial sorting)
using OrderedDict (all duplicates removed)
using sorted(list(set(_))) (all duplicaties removed, ordering restored by sorting).
I compared the running times of the different solutions using:
from timeit import timeit
print('groupby:', timeit('from itertools import groupby; l = [x // 5 for x in range(1000)]; [k for k, _ in groupby(l)]'))
print('OrderedDict:', timeit('from collections import OrderedDict; l = [x // 5 for x in range(1000)]; list(OrderedDict.fromkeys(l))'))
print('Set:', timeit('l = [x // 5 for x in range(1000)]; sorted(list(set(l)))'))
> groupby: 78.83623623599942
> OrderedDict: 94.54144410200024
> Set: 65.60372123999969
Note that the set approach is the fastest among all alternatives.
Old Answer
Python first evaluates the list comprehension and then assigns it to newList, so you cannot refer to it during execution of the list comprehension. To illustrate, consider the following code:
randList = [1, 2, 2, 3, 4, 4, 5]
newList = []
newList = [num for num in randList if print(newList)]
> []
> []
> []
> …
This becomes even more evident if you try:
# Do not initialize newList2
newList2 = [num for num in randList if print(newList2)]
> NameError: name 'newList2' is not defined
You can remove duplicates by turning randList into a set:
sorted(list(set(randlist)))
> [1, 2, 3, 4, 5]
Be aware that this does remove all duplicates (not just adjacent ones) and ordering is not preserved. The former also holds true for your proposed solution with the loop.
edit: added a sorted clause as to specification of required ordering.
In this line newList = [num for num in randList if num not in newList], at first the list will be created in right side then then it will be assigned to newList. That's why every time you check if num not in newList returns True. Becasue newList remains empty till the assignment.
You can try this:
randList = [1, 2, 2, 3, 4, 4, 5]
new_list=[]
for i in randList:
if i not in new_list:
new_list.append(i)
print(new_list)
You cannot access the items in a list comprehension as you go along. The items in a list comprehension are only accessible once the comprehension is completed.
For large lists, checking for membership in a list will be expensive, albeit with minimal memory requirements. Instead, you can append to a set:
randList = [1, 2, 2, 3, 4, 4, 5]
def gen_values(L):
seen = set()
for i in L:
if i not in seen:
seen.add(i)
yield i
print(list(gen_values(randList)))
[1, 2, 3, 4, 5]
This algorithm has been implemented in the 3rd party toolz library. It's also known as the unique_everseen recipe in the itertools docs:
from toolz import unique
res = list(unique(randList))
Since your list is sorted, using set will be the fasted way to achieve your goal, as follows:
>>> randList = [1, 2, 2, 3, 4, 4, 5]
>>> randList
[1, 2, 2, 3, 4, 4, 5]
>>> remove_dup_list = list(set(randList))
>>> remove_dup_list
[1, 2, 3, 4, 5]
>>>
I am new to python and trying to simply replace the values of a list when they meet a condition with values from a shorter list.
For example:
list1 = [1,0,1,0,1,0]
list2 = [1,2,3]
The output I want is:
list1 = [1,1,1,2,1,3]
I can use a loop with a counter:
counter = 0
for i, j in enumerate(list1):
if j == 1:
list1[i] = list2[counter]
counter += 1
But this seems inefficient for something so simple, so I'm guessing there might be a way to do this with a list comprehension, something like:
[list2[i] if j == 0 else j for i,j in enumerate(list1)]
(although this fails due the lists being different lengths).
Is there any other concise way of doing this in base python, perhaps using map or filter?
You can use an iterator made from the short list and just call next on it in the comprehension:
list1 = [1, 0, 1, 0, 1, 0]
list2 = [1, 2, 3]
it2 = iter(list2)
[x if x != 0 else next(it2) for x in list1]
# [1, 1, 1, 2, 1, 3]
Note that you can provide a default value to next if there are not enough filler elements:
Try something like:
[x if x else list2.pop(0) for x in list1]
Note this removes items from list2.
If you want to use map(), you could try this:
from collections import deque
list1 = [1,0,1,0,1,0]
list2 = [1,2,3]
queue = deque(list2)
result = list(map(lambda x : x if x else queue.popleft(), list1))
print(result)
Which outputs:
[1, 1, 1, 2, 1, 3]
Note: I used a stack/queue data structure, collections.deque to allow a O(1) popleft() from the front, instead of using pop(0), which is O(n). If you don't wish to use this library, you can just reverse list2 beforehand, and call pop(), which is also O(1).
You can try this:
list1 = [1,0,1,0,1,0]
list2 = [1,2,3]
new_list = [list2[list1[:i].count(a)] if a != 1 else a for i, a in enumerate(list1)]
Output:
[1, 1, 1, 2, 1, 3]
I want to write a function that compares the first element of this list with the last element of this list, the second element of this list with the second last element of this list, and so on. If the compared elements are the same, I want to add the element to a new list. Finally, I'd like to print this new list.
For example,
>>> f([1,5,7,7,8,1])
[1,7]
>>> f([3,1,4,1,5]
[1,4]
>>> f([2,3,5,7,1,3,5])
[3,7]
I was thinking to take the first (i) and last (k) element, compare them, then raise i but lower k, then repeat the process. When i and k 'overlap', stop, and print the list. I've tried to visualise my thoughts in the following code:
def f(x):
newlist=[]
k=len(x)-1
i=0
for j in x:
if x[i]==x[k]:
if i<k:
newlist.append(x[i])
i=i+1
k=k-1
print(newlist)
Please let me know if there are any errors in my code, or if there is a more suitable way to address the problem.
As I am new to Python, I am not very good with understanding complicated terminology/features of Python. As such, it would be encouraged if you took this into account in your answer.
You could use a conditional list comprehension with enumerate, comparing the element x at index i to the element at index -1-i (-1 being the last index of the list):
>>> lst = [1,5,7,7,8,1]
>>> [x for i, x in enumerate(lst[:(len(lst)+1)//2]) if lst[-1-i] == x]
[1, 7]
>>> lst = [3,1,4,1,5]
>>> [x for i, x in enumerate(lst[:(len(lst)+1)//2]) if lst[-1-i] == x]
[1, 4]
Or, as already suggested in other answers, use zip. However, it is enough to slice the first argument; the second one can just be the reversed list, as zip will stop once one of the argument lists is finished, making the code a bit shorter.
>>> [x for x, y in zip(lst[:(len(lst)+1)//2], reversed(lst)) if x == y]
In both approaches, (len(lst)+1)//2 is equivalent to int(math.ceil(len(lst)/2)).
maybe you want something like for even length of list:
>>> r=[l[i] for i in range(len(l)/2) if l[i]==l[-(i+1)]]
>>> r
[3]
>>> l=[1,5,7,7,8,1]
>>> r=[l[i] for i in range(len(l)/2) if l[i]==l[-(i+1)]]
>>> r
[1, 7]
And for odd length of list :
>>> l=[3,1,4,1,5]
>>> r=[l[i] for i in range(len(l)/2+1) if l[i]==l[-(i+1)]]
>>> r
[1, 4]
so you can create a function :
def myfunc(mylist):
if (len(mylist) % 2 == 0):
return [l[i] for i in range(len(l)/2) if l[i]==l[-(i+1)]]
else:
return [l[i] for i in range(len(l)/2+1) if l[i]==l[-(i+1)]]
and use it this way :
>>> l=[1,5,7,7,8,1]
>>> myfunc(l)
[1, 7]
>>> l=[3,1,4,1,5]
>>> myfunc(l)
[1, 4]
What you can do is zip over the first half and the second half reversed and use list comprehensions to build a list of the same ones:
[element_1 for element_1, element_2 in zip(l[:len(l)//2], reversed(l[(len(l)+1)//2:])) if element_1 == element_2]
What happens is that you take the first half and iterate over those as element_1, the second half reversed as element_2 and then only add them if they are the same:
l = [1, 2, 3, 3, 2, 4]
l[:len(l)//2] == [1, 2, 3]
reversed(l[(len(l)+1)//2:])) == [4, 2, 3]
1 != 4, 2 == 2, 3 == 3, result == [2, 3]
If you also want to include the middle element in the case of an odd list, we can just extend our lists to both include the middle element, which will always evaluate as the same:
[element_1 for element_1, element_2 in zip(l[:(len(l) + 1)//2], reversed(l[len(l)//2:])) if element_1 == element_2]
l = [3, 1, 4, 1, 5]
l[:len(l)//2] == [3, 1, 4]
reversed(l[(len(l)+1)//2:])) == [5, 1, 4]
3 != 5, 1 == 1, 4 == 4, result == [1, 4]
Here is my solution:
[el1 for (el1, el2) in zip(L[:len(L)//2+1], L[len(L)//2:][::-1]) if el1==el2]
There is a lot going on, so let me explain step by step:
L[:len(L)//2+1] is the first half of the list plus an extra element (which is useful for lists of odd lengths)
L[len(L)//2:][::-1] is the second half of the list, reversed ([::-1])
zip creates a list of pairs from two lists. it stops at the end of the shortest list. We use this in the case the length of the list is even, so the extra term in the first half is neglected
List comprehension essentially equivalent to a for loop, but useful to create a list "on the fly". It will return an element only if the if condition is true, otherwise it will pass.
You can easily modify the solution above if you are interested in the indexes (of the first half) where the match occurs:
[idx for idx, (el1, el2) in enumerate(zip(L[:len(L)//2+1], L[len(L)//2:][::-1])) if el1==el2]
You can use the following which leverages from zip_longest:
from itertools import zip_longest
def compare(lst):
size = len(lst) // 2
return [y for x, y in zip_longest(lst[:size], lst[-1:size-1:-1], fillvalue=None) if x == y or x is None]
print(compare([1, 5, 7, 7, 8, 1])) # [1, 7]
print(compare([3, 1, 4, 1, 5])) # [1, 4]
print(compare([2, 3, 5, 7, 1, 3, 5])) # [3, 7]
On zip_longest:
Normally, zip stops zipping when one of its iterators run out. zip_longest does not have that limitation and it simply keeps on zipping by adding dummy values.
Example:
list(zip([1, 2, 3], ['a'])) # [(1, 'a')]
list(zip_longest([1, 2, 3], ['a'], fillvalue='z')) # [(1, 'a'), (2, 'z'), (3, 'z')]
I want to create what I thought was a fairly straightforward function. The function just runs through a list of lists and returns any list that does not have a 1 in all of the list elements following the second element ([2: ]). So given the list of lists [[1, 2, 1, 1, 1, 1], [4, 5, 1, 2, 0.3, 1, 1, 1]] the function would return [4, 5, 1, 2, 0.3, 1, 1, 1]. What I have so far is:
def discover(A):
"""Looks for list that has an element not equal to one.
"""
for i in range(len(A)):
for j in range(len(A[i])):
if A[i][j+2] != 1:
print A[i]
But when I run the function it finds one list but then prints that list over and over again before giving me an IndexError saying the list index is out of range. This seems to be a fairly easy problem but for some reason I'm not getting it. Any help would be really appreciated.
The problem is these two lines:
for j in range(len(A[i])):
if A[i][j+2] != 1:
What'll happen is that you'll eventually get to a point where j is the length of your list, minus 1. But then you're calling j+2 in the below code, and that's guaranteed to create a number longer than your list, giving you the IndexError. You can fix that with:
for j in range(2,len(A[i])):
if A[i][j] != 1:
As for the endless printing, you're nearly there, but you'll want to stop the loop if you find the non-1 element.
if A[i][j] != 1:
print A[i]
break
Alternately, the other answers will give you the same result more easily. But that's where your current errors are coming from.
for list in A:
if 1 not in list[3:]:
print list
even another solution:
lst = [
[1,2,3],
[1,1,1],
[3,4,5],
[3,5,6],
] # +++
def has1(subLst):
return subLst.count(1) == 0
print filter(has1, lst)
This avoids out of range issues.
def discover(A):
results = []
for lst in A:
for i in lst[3:]:
if i != 1:
results.append(lst)
break
return results
In addition to the other answers here, one could also make use of a generator. The yield statement will allow you to skirt establishing a default list to place your results into; you can just specify the condition you're looking for and yield the result.
>>> def discover(lists):
... for l in lists:
... if not [x for x in l[2:] if x != 1]:
... yield l
>>> stuff = [[2, 3, 4, 5, 1, 2], [2, 5, 1, 1, 1, 1, 1]]
>>> results = discover(stuff) #returns <generator object discover at 0x105e3eb90>
>>> results.next()
[2, 5, 1, 1, 1, 1, 1]
>>>
The magic line here being, if not [x for x in l[2:] if x !=1]. It builds a list from l[2:] and checks that any variable in there does not equal 1; if the list has no length, it means there are no non-1 entries in l[2:] and so it yields l.
A query to check if any element (after the second) != 1 would be:
any(x != 1 for x in mylist[3:])
so
def discover(A):
for mylist in A:
if any(x != 1 for x in mylist[3:]):
print mylist