Is list comprehension appropriate here? - python

I have to append elements to a list only if the current iterated element is not already in the list.
>>> l = [1, 2]
>>> for x in (2, 3, 4):
... if x not in l:
... l.append(x)
...
>>> l
[1, 2, 3, 4]
vs
>>> l = [1, 2]
>>> [l.append(i) for i in (2, 3, 4) if i not in l]
[None, None]
>>> l
[1, 2, 3, 4]
The list comprehension gives the result is what I want, just the returned list is useless. Is this a good use case for list comprehensions?
The iteration is a good solution, but I'm wondering if there is a more idiomatic way to do this?

This algorithm, either with or without a list comprehension, is not as efficient as possible; list.__contains__ is O(n), and so adding the elements of another list to it is O(n2). On the other hand, set.__contains__ is O(log n), so the best way to do this is to use a set to check for membership, and a list to preserve order. That way you're doing n operations that are O(log n), for a total of O(n log n), which much faster than O(n2) for reasonable values of n (above say, 100 elements).
>>> l = [1, 2]
>>> seen = set(l)
>>> for x in (2, 3, 4):
... if x not in seen:
... seen.add(x)
... l.append(x)
...
>>> l
[1, 2, 3, 4]
>>>

You could do:
l.extend((i for i in (2,3,4) if i not in l))
This solution still works if the added list is non-unique.

Using list comprehensions just for sideeffects is discouraged. There is nothing wrong with the 3 line version.
If l gets really long, you may want to maintain a set in parallel to avoid using in l on the long list

I can suggest one more solution:
orig = [1,2]
ext = [2,3,4]
orig.extend(filter( lambda x,p=set(orig):not(x in p or p.add(x)),ext ))
It takes into account element order and works in case of element repetition.
BTW, complexity is O(n*log(n)).

Related

How to iterate the first index value twice before going to the next index position?

I'm trying to make a for loop that iterates each index twice before going to the next one, for example if I have the following list:
l = [1,2,3]
I would like to iterate it if it was in this way:
l = [1,1,2,2,3,3]
could someone help me with this problem please?
The most obvious thing would be a generator function that yields each item in the iterable twice:
def twice(arr):
for val in arr:
yield val
yield val
for x in twice([1, 2, 3]):
print(x)
If you need a list, then
l = list(twice([1, 2, 3]))
You could make a list comprehension that repeats the elements and flattens the result:
l = [1,2,3]
repeat = 2
[n for i in l for n in [i]*repeat]
# [1, 1, 2, 2, 3, 3]
You can solve this by using NumPy.
import numpy as np
l = np.repeat([1,2,3],2)
Repeat repeats elements of an array the specified number of times. This also returns a NumPy array. This can be converted back to a list if you wish with list(l). However, NumPy arrays act similar to lists so most of the time you don't need to change anything.
Unsurprisingly, more-itertools has that:
>>> from more_itertools import repeat_each
>>> for x in repeat_each([1, 2, 3]):
... print(x)
...
1
1
2
2
3
3
(It also has an optional second parameter for telling it how often to repeat each. For example repeat_each([1, 2, 3], 5). Default is 2.)
l = [1, 2, 3]
lst = []
for x in l:
for i in range(2):
lst.append(x)
print(lst)
# [1, 1, 2, 2, 3, 3]

fastest way find the common elements of two lists without changing the sequence of first list

I have two lists,
l1 = [1,4,3,2,5]
l2 = [4,3,2,10]
Now I want to find the common element between to lists so I am using following code,
list(set(l1).intersection(set(l2)))
>> [2, 3, 4]
But its changing the sequence of the l1, I don't want to change the sequence so the result should be ,
>> [4, 3, 2]
Looking for the fastest way to do this without changing the sequence ?
You can re-sort the result using l1:
>>> sorted(set(l1).intersection(set(l2)), key=l1.index)
[4, 3, 2]
You could have also used a list comprehension instead of a set intersection but I believe the first method would be generally faster depending on the number of elements in each list, because searching a list is O(n) and the solution below becomes O(n*m):
>>> [i for i in l1 if i in l2]
[4, 3, 2]
Finally, you can optimise the comprehension method by converting l2 to a set:
>>> s2 = set(l2)
>>> [i for i in l1 if i in s2]
[4, 3, 2]
Let's say you want to preserve l1's sequence. An algorithm of O(n^2) time complexity would be to find for every item x in l1 if x exists in l2. Following code would work
common_elements = [x for x in l1 if x in l2]
You can reduce time complexity to O(n) by using a map(dict)
lookup_map = {x: True for x in l2}
common_elements = [x for x in l1 if x in lookup_map]
Edit: Found out that Python's set is implemented as a hash-map and lookups take O(1) in average case https://wiki.python.org/moin/TimeComplexity, so following should also have an avg. case complexity of O(n)
common_elements = [x for x in l1 if x in set(l2)]
The fastest way depends on data, but this solution should be one of the fastest
[x for x in [1, 4, 3, 2, 5] if x in {4, 3, 2, 10}]

Python list comprehension and do action

Is it possible to do an action with the item in a list comprehension?
Example:
list = [1, 2, 3]
list_filtered = [ i for i in list if i == 3 AND DO SOMETHING WITH I]
print (list_filtered)
For example if I want to remove the '3' how would I go about it? Logic says that it's something like:
list = [1, 2, 3]
list_filtered = [ i for i in list if i == 3 && list.remove(i) ]
print (list_filtered)
I can't seem to make Python perform an action with that 'i' with any syntax that I tried. Can someone please elaborate?
EDIT: Sorry, the explanation might not be clear enough. I know how to iterate and create the new list. I want to create "list_filtered" and remove that value from "list" if it fits the "IF" statement.
Practically I need the following:
list = [1, 2, 3]
list_filtered = [ i for i in list if i == 3 && list.remove(i) ]
print (list_filtered)
# output >> [3]
print (list)
# output >> [1, 2]
I hope the above makes it more clear. Also, please note that my question is if this can be done in the list comprehension specifically. I know how to do it with additional code. :)
EDIT2: Apparently what I wanted to do isn't possible and also isn't advisable (which is the reason it isn't possible). It seemed like a logical thing that I just didn't know how to do. Thanks guys :)
If you're just trying to remove 3 you could do:
list_filtered=[i for i in list if i != 3]
print(list_filtered) # [1,2]
This will remove all values that are not equal to 3.
Alternatively if you wanted to do something like increment all the items in the list you would do:
[i+1 for i in list]
>>> [2,3,4]
Using a function on every item of the list would look like:
[float(i) for i in list]
>>> [1.0, 2.0, 3.0]
You can do ternary statements:
[i if i<3 else None for i in list]
>>>[1, 2, None]
and a whole lot more...
Here is more documentation on list comprehensions.
Given your new updates, I would try something like:
list_filtered=[list.pop(list.index(3))]
Then list_filtered would be [3] and list would be [1,2] as your specified.
Your miss understand the purpose of list comprehension. List comprehension should be used to create a list, not to use its side effects. Further more, as Leijot has already mentioned, you should never modify a container while iterating over it.
If you want to filter out certain elements in a list using list comprehension, use an if statement:
>>> l = [1, 2, 3]
>>> l_filtered = [i for i in l if i != 3]
>>> l_filtered
[1, 2]
>>>
Alternatively, you can use the builtin function filter():
>>> l = [1, 2, 3]
>>> l_filtered = list(filter(lambda x: x != 3, l))
>>> l_filtered
[1, 2]
>>>
Edit: Based on your most recent edit, what your asking is absolutely possible. Just use two different list comprehensions:
>>> l = [1, 2, 3]
>>> l_filtered = [i for i in l if i == 3]
>>> l_filtered
[3]
>>> l = [i for i in l if i != 3] # reassigning the value of l to a new list
>>> l
[1, 2]
>>>
Maybe you want to provide a different example of what you're trying to do since you shouldn't remove elements from a list while you're iterating over it. Since list comprehensions create another list anyway, you could accomplish the same thing as your example by building a list that omits certain elements. If your goal is to create a list that excludes the value 3, to slightly modify your example:
l = [1, 2, 3]
list_filtered = [ i for i in l if i != 3 ]
l remains unaltered, but list_filtered now contains [1, 2].

Compare i vs other items in a Python list

Good Day, I've googled this question and have found similar answers but not what I am looking for. I am not sure what the problem is called so I that doesn't help me and I am looking for an elegant solution.
How do I loop over a list, item at a time, and compare it to all other items in a list. For example, if I had a list
l = [1,2,3,4]
Each loop of the out would yield something like
1 vs [2,3,4]
2 vs [1,3,4]
3 vs [1,2,4]
4 vs [1,2,3]
One solution I've been playing with involves duplicating the list every iteration, finding the index of the item, deleting it from the duplicate list and compare the two. This route seems less ideal as you have to create a new list on every iteration.
You can use itertools.combiations to create all combinations of the length 3 from your list and then use set.defference method to get the difference element between the l and the combinations. but note that you need to convert your main list to a set object :
>>> from itertools import combinations
>>> l = {1,2,3,4}
>>> [(l.difference(i).pop(),i) for i in combinations(l,3)]
[(4, (1, 2, 3)), (3, (1, 2, 4)), (2, (1, 3, 4)), (1, (2, 3, 4))]
A simple approach would be to use two loops:
arr = [1,2,3,4]
for i in arr:
comp = []
for j in arr:
if i != j:
comp.append(j)
print(comp)
I guess you could use list comprehension. While still creating a new list every iteration, you don't need to delete an item each time:
l = [1,2,3,4]
for i in l:
temp = [item for item in l if item != i]
print temp
[2, 3, 4]
[1, 3, 4]
[1, 2, 4]
[1, 2, 3]

modifying elements list of tuples python

As I know a tuple is immutable structure, so if I have a list of tuples.
list1 = [(1,2,3,4),(2,3,4,5)]
and I have to change the first element of a tuple then I will have to basically
write:
list1[0] = (2,2,3,4) not list1[0][0] = 2 because tuple is immutable
For each element I need to do this. Is this an efficient operation or is it better to use a list of lists if this operation needs to be done regularly?
If you need to modify the elements of the list, then use mutable elements. Storing an immutable object in a mutable container does not make the object mutable.
As for efficiency, constructing a new tuple is more expensive than modifying a list. But readability is more important than runtime performance for most operations, so optimize for readability first. Also keep in mind that when the elements of a list of lists are referenced from the outside, you might get side-effects:
l1 = [1, 2, 3]
l2 = ['a', 'b', 'c']
lol = [l1, l2]
lol[0][0] = 0
print(l1) # prints [0, 2, 3]
UPDATE: to back my claim of efficiency, here's some timings using IPython's %timeit magic:
>>> list_of_lists = [[1,2,3,4] for _ in xrange(10000)]
>>> list_of_tuples = map(tuple, list_of_lists)
>>> def modify_lol():
... for x in list_of_lists:
... x[0] = 0
...
>>> def modify_lot():
... for i, x in enumerate(list_of_tuples):
... list_of_tuples[i] = (0,) + x[1:]
...
>>> %timeit modify_lol()
100 loops, best of 3: 6.56 ms per loop
>>> %timeit modify_lot()
100 loops, best of 3: 17 ms per loop
So lists of lists are 2.6× faster for this task.
Well looking at the direct solution you have the equivalent of
list[0] = (2,) + list[0] [1:]
Which should be enough to allow you to do it programmatically. It's still making a copy, but that's fairly quick, as is slicing the tuple.
If you define your variable like that:
list1 = [[1,2,3,4],[2,3,4,5]]
You will able to change the value of an elemento of a list like that:
list1[0][0] = 2
Now your variable value will be:
list1 = [[2,2,3,4],[2,3,4,5]]
The inefficently will come when you have to change ONLY a specific element. For instance say you had two tuples.
tup1 = (1, 2, 3, 4)
tup2 = (5, 6, 7, 8)
And you wanted to change the first element of both tuples to 9.
tup1 = (9, 2, 3, 4)
tup2 = (9, 6, 7, 8)
Is the only way to do that, if you had a million tuples with different values that all needed to start with 9 then this way is obviously inefficent, you will have to type and reassign all tuples different values.
Instead you should use a list.
l1 = [1, 2, 3, 4]
l2 = [5, 6, 7, 8]
Then you can do
list = [l1, l2]
for l in list:
l[0] = 9
Which will change all the first elements to 9 without hardcoding a million lists.
l1 is now [9, 2, 3, 4]
l2 is now [9, 6, 7, 8]
You have two options:
L = [(1,2,3,4),(2,3,4,5)]
L = [tuple([2]+subL[1:]) for subL in L]
This is slower, as it has to recreate all those tuples.
OR
L = [(1,2,3,4),(2,3,4,5)]
L = [list(i) for i in L] # or L = map(list, L)
for subL in L:
subL[0] = 2
If you know you'll only ever have four elements in your list items, but they need to be mutable, the best option (in my opinion) is to use a class:
class WXYZ: # example name, give yours something more sensible to the nature of the code
def __init__(self, w=0, x=0, y=0, z=0)
self.w = w
self.x = x
self.y = y
self.z = z
Then your code will look something like:
list1 = [WXYZ(1,2,3,4), WXYZ(2,3,4,5)]
list1[0].w = 2
You can also easily add an __iter__ method if you need to use it in a for loop:
def __iter__(self):
return iter((w,x,y,z))
Alternatively, if you want to get clever, you can do
def __iter__(self):
yield w
yield x
yield y
yield z
If you're really worried about memory usage (why are you using python in that case?), you can define __slots__ = ("w","x","y","z") to assign exactly as much memory as is required.

Categories