list comprehension with iteration - python

Suppose i have a list L which consists of integer elements. I want to construct a list T which for each index i in 0..len(L) contains the item L[i] provided it is larger than 0 using List comprehension in python.
I tried the following command to do this
T=[L[i] if L[i]>0 for i in range(len(L))]
but i keep getting an error of invalid syntax. How would i do this correctly using List comprehensions in python?

Your syntax is wrong :
L = [1,2,-4,5,-6,7,8,9]
T = [L[i] for i in range(len(L)) if L[i]>0]
Output:
[1, 2, 5, 7, 8, 9]
Also you can just iterate through the elements of the list per se, no need to use a range. Like this : T = [i for i in L if i>0]
Remember :
When only if is there the syntax is [expression for var in list if ...]
When both if and else is there the syntax is [expression1 if ... else expression2 for var in list]

You need to write the filter after the iteration part. So:
T=[L[i] for i in range(len(L)) if L[i]>0]
# \__/ \_______ ____________/ \___ ___/
# yield v v
# iteration filter
Right now Python thinks that you want to write a ternary operator, like:
T=[L[i] if L[i] > 0 else 0 for i in range(len(L))]
This is incorrect: here you would evaluate L[i] if L[i] > 0 else 0 for every element, and you would thus add a 0 for every item L[i] where the element is less than or equal to zero.
That being said, you can write your list comprehension more elegant (and faster), with:
T = [l for l in L if l > 0]
So instead of iterating over indices, we iterate over the elements l in L. We also filter on l and yield l in case the filtering is successful.

While you can use a list comprehension to solve this problem, you can also use filter with a lambda function:
final_l = list(filter(lambda x:x > 0, L))

Related

Save list number within a list only if it contains elements in python

I have list of lists such as :
my_list_of_list=[['A','B','C','E'],['A','B','C','E','F'],['D','G','A'],['X','Z'],['D','M'],['B','G'],['X','Z']]
as you can see, the list 1 and 2 share the most elements (4). So, I keep a list within my_list_of_list only if the 4 shared elements (A,B,C or E) are present within that list.
Here I then save within the list_shared_number[], only the lists 1,2,3 and 6 since the other does not contain (A,B,C or E).
Expected output:
print(list_shared_number)
[0,1,2,5]
Probably sub optimal because I need to iterate 3 times over lists but it's the expect result:
from itertools import combinations
from functools import reduce
common_elements = [set(i).intersection(j)
for i, j in combinations(my_list_of_list, r=2)]
common_element = reduce(lambda i, j: i if len(i) >= len(j) else j, common_elements)
list_shared_number = [idx for idx, l in enumerate(my_list_of_list)
if common_element.intersection(l)]
print(list_shared_number)
# Output
[0, 1, 2, 5]
Alternative with 2 iterations:
common_element = {}
for i, j in combinations(my_list_of_list, r=2):
c = set(i).intersection(j)
common_element = c if len(c) > len(common_element) else common_element
list_shared_number = [idx for idx, l in enumerate(my_list_of_list)
if common_element.intersection(l)]
print(list_shared_number)
# Output
[0, 1, 2, 5]
You can find shared elements by using list comprehension. Checking if index 0 and index 1:
share = [x for x in my_list_of_list[0] if x in my_list_of_list[1]]
print(share)
Assume j is each item so [j for j in x if j in share] can find shared inner elements. if the length of this array is more than 0 so it should include in the output.
So final code is like this:
share = [x for x in my_list_of_list[0] if x in my_list_of_list[1]]
my_list = [i for i, x in enumerate(my_list_of_list) if len([j for j in x if j in share]) > 0]
print(my_list)
You can use itertools.combinations and set operations.
In the first line, you find the intersection that is the longest among pairs of lists. In the second line, you iterate over my_list_of_list to identify the lists that contain elements from the set you found in the first line.
from itertools import combinations
comparison = max(map(lambda x: (len(set(x[0]).intersection(x[1])), set(x[0]).intersection(x[1])), combinations(my_list_of_list, 2)))[1]
out = [i for i, lst in enumerate(my_list_of_list) if comparison - set(lst) != comparison]
Output:
[0, 1, 2, 5]
Oh boy, so mine is a bit messy, however I did not use any imports AND I included the initial "finding" of the two lists which have the most in common with one another. This can easily be optimised but it does do exactly what you wanted.
my_list_of_list=[['A','B','C','E'],['A','B','C','E','F'],['D','G','A'],['X','Z'],['D','M'],['B','G'],['X','Z']]
my_list_of_list = list(map(set,my_list_of_list))
mostIntersects = [0, (None,)]
for i, IndSet in enumerate(my_list_of_list):
for j in range(i+1,len(my_list_of_list)):
intersects = len(IndSet.intersection(my_list_of_list[j]))
if intersects > mostIntersects[0]: mostIntersects = [intersects, (i,j)]
FinalIntersection = set(my_list_of_list[mostIntersects[1][0]]).intersection(my_list_of_list[mostIntersects[1][1]])
skipIndexes = set(mostIntersects[1])
for i,sub_list in enumerate(my_list_of_list):
[skipIndexes.add(i) for char in sub_list
if i not in skipIndexes and char in FinalIntersection]
print(*map(list,(mostIntersects, FinalIntersection, skipIndexes)), sep = '\n')
The print provides this :
[4, (0, 1)]
['E', 'C', 'B', 'A']
[0, 1, 2, 5]
This works by first converting the lists to sets using the map function (it has to be turned back into a list so i can use len and iterate properly) I then intersect each list with the others in the list of lists and count how many elements are in each. Each time i find one with a larger number, i set mostIntersections equal to the len and the set indexes. Once i go through them all, i get the lists at the two indexes (0 and 1 in this case) and intersect them to give a list of elements [A,B,C,E] (var:finalIntersection). From there, i just iterate over all lists which are not already being used and just check if any of the elements are found in finalIntersection. If one is, the index of the list is appended to skipIndexes. This results in the final list of indexes {indices? idk} that you were after. Technically the result is a set, but to convert it back you can just use list({0,1,2,5}) which will give you the value you were after.

Remove duplicates without using list mutation

I am trying to remove adjacent duplicates from a list without using list mutations like del or remove. Below is the code I tried:
def remove_dups(L):
L = [x for x in range(0,len(L)) if L[x] != L[x-1]]
return L
print(remove_dups([1,2,2,3,3,3,4,5,1,1,1]))
This outputs:
[1, 3, 6, 7, 8]
Can anyone explain me how this output occurred? I want to understand the flow but I wasn't able to do it even with debugging in VS code.
Input:
[1,2,2,3,3,3,4,5,1,1,1]
Expected output:
[1,2,3,4,5,1]
I'll replace the variables to make this more readable
def remove_dups(L):
L = [x for x in range(0,len(L)) if L[x] != L[x-1]]
becomes:
def remove_dups(lst):
return [index for index in range(len(lst)) if lst[index] != lst[index-1]]
You can see, instead of looping over the items of the list it is instead looping over the indices of the array comparing the value at one index lst[index] to the value at the previous index lst[index-1] and only migrating/copying the value if they don't match
The two main issues are:
the first index it is compared to is -1 which is the last item of the list (compared to the first)
this is actually returning the indices of the non-duplicated items.
To make this work, I'd use the enumerate function which returns the item and it's index as follows:
def remove_dups(lst):
return [item for index, item in enumerate(lst[:-1]) if item != lst[index+1]] + [lst[-1]]
Here what I'm doing is looping through all of the items except for the last one [:-1] and checking if the item matches the next item, only adding it if it doesn't
Finally, because the last value isn't read we append it to the output + [lst[-1]].
This is a job for itertools.groupby:
from itertools import groupby
def remove_dups(L):
return [k for k,g in groupby(L)]
L2 = remove_dups([1,2,2,3,3,3,4,5,1,1,1])
Output: [1, 2, 3, 4, 5, 1]

Concise a for loop (x for x in ...) in python

I am trying to concise the following simple loop
a = [1,2,3,2,1,5,6,5,5,5]
for x in set(a):
a.remove(x)
This is working well but I need to know if it is possible to apply the concise for loop like that
a = [x for x in set(a):a.remove(x)]
My desire output is to get or list the duplicates only and get list of them, so the desired output is [1,2,5]
The code is working well
a = [1,2,3,2,1,5,6,5,5,5]
for x in set(a):
a.remove(x)
print(list(set(a)))
My target is not the code but to concise the loop in the loop. I need to learn this trick.
** Found a simple and effective solution:
print(list(set([x for x in a if a.count(x) > 1])))
Original question
a = a if not all([a.remove(i) for i in set(a) ]) else []
print(a)
As suggested by Copperfield, the following also works:
a = any(a.remove(i) for i in set(a) ) or a
Updated question
from collections import Counter
a = [1,2,3,2,1,5,6,5,5,5]
print([k for k, v in Counter(a).items() if v > 1])
find dups in a list
print ([c for i,c in enumerate(a) if a.count(c) > 1 and i==a.index(c)])
The output of this will be:
[1, 2, 5]
alternate for set(a)
Here's the list comprehension to create the same result as
print(list(set(a)))
This can be achieved by doing the following:
print([c for i,c in enumerate(a) if i==a.index(c)])
Here I am checking if the element c is the first time we encountered (index is i) and if yes, then add to list else ignore.
The output of both these will be:
[1, 2, 3, 5, 6]
While the output is the same, I would strongly recommend using the first method than doing a for loop and checking for index. The cost is too high to do this compared to list(set(a))
You can just do:
a = [1,2,3,2,1,5,6,5,5,5]
[a.remove(x) for x in set(a)]
print(a)
a will have the same items as after your for loop.
You can read more about list comprehensions.

return from list given one or another condition in python

Let's say I have list = [1,2,3,'hello','bye',10,11,12,12.2,12.3] I want to return the elements that are in an odd index (zero based) OR they are integers. The idea is to make it the code as simple as possible.
Thanks!
You can use list comprehension:
[x for i,x in enumerate(lst) if i%2 == 1 or isinstance(x,int)]
Here we make use of enumerate(..) to generate tuples (i,x) with the index (zero-based) and the element. The if in the list filters: only elements where i%2 == 1 (odd index) or where x is an instance of int are allowed. For these elements, we add x to the result.
The result is:
>>> [x for i,x in enumerate(lst) if i%2 == 1 or isinstance(x,int)]
[1, 2, 3, 'hello', 10, 11, 12, 12.3]
Please do not use list as variable name: it is the name of a class, so by using list, you can no longer use list(..). This answer works with lst.

Delete item from list upon a condition

I have a list of lists of tuples of integers.
ls = [[(a_1, a_2), (b_1, b_2)], [(c_1, c_2), (d_1, d_2), (e_1, e_2)], ...]
And I need to delete every item of ls that contains a tuple whose second entry is equal to a predetermined integer.
I tried this:
for item in ls:
for tpl in item:
if tpl[1] == m:
ls.remove(item)
But for some reason, this only removes a few of the list items but not all containing a tuple with second entry = m.
Use a list comprehension:
ls = [item for item in ls if all(tuple[1] != m for tuple in item)]
Or use a filter:
ls = filter(lambda item: all(tuple[1] != m for tuple in item),ls)
Code sucks and we need less of it - here's as sparse as it gets.
[l for l in ls if m not in [i[1] for i in l]]
The best way to filter a list in python is to use a list comprehension:
filtered = [item for item in ls if not(contains_m(item))]
And then all you need is a function that can tell if an item contains m, for example:
def contains_m(item):
return any([tpl[1] == m for tpl in item])
Removing an itme from list is not a good idea while iterating though it.
Try that (if where are talking Python here)
ls = [[('a_1', 'a_2'), ('b_1', 'b_2')], [('c_1', 'c_2'), ('d_1', 'd_2'), ('e_1', 'e_2')]]
m='c_2'
print [ x for x in ls if not [ y for y in x if y[1]==m ]]
Python's list iterator is lazy. This means that when you remove an item from the list, it will skip the next item. For example, say you want to remove all ones from the following list:
[1, 1, 2]
Your for loop starts at index 0:
[1, 1, 2]
^
It removes the element and moves on:
[1, 2]
^
This example is just to help illustrate the issue. One simple workaround is to loop backwards using the index:
for ind in range(len(ls)-1, -1, -1):
item = ls[ind]
for tpl in item:
if tpl[1] == m:
del ls[ind]
break # You can end the inner loop immediately in this case
Another way is to make a copy of the list to iterate over, but remove from the original:
for item in ls[:]:
for tpl in item:
if tpl[1] == m:
ls.remove(item)
break
The last approach can be simplified into creating an output list that contains only the elements that you want. This is easiest to do with a list comprehension. See #AlexeySmirnov 's answer for the best way to do that.

Categories