Related
I am looking for an elegant way to slice a list l in python, given a list of ids l_ids.
For example, instead of writing
new_list = [l[i] for i in l_ids]
Write something like (Pseudo code):
new_list = l[*l_ids]
Is there a similar way to slice lists?
I have the feeling that someone have asked that already, but I couldn't find any reference for it.
Edit: It is OK to assume that all the list items are of the same type?
You can use operator.itemgetter(*items) like this:
from operator import itemgetter
getter = itemgetter(*lst_ids)
new_list = list(getter(lst))
Also, note that I renamed l variable to lst because it is less ambiguous and should be avoided.
You can implicitly cast the tuple to a list using Python 3 unpacking, as #JonClements commented:
*new_list, = getter(lst)
Finally, since Python 3.5, you can also use extended unpacking:
new_list = [*getter(lst)]
You could use itemgetter
from operator import itemgetter
l = ['a', 'b', 'c', 'd', 'e']
l_ids = [1, 2, 3]
list(itemgetter(*l_ids)(l))
['b', 'c', 'd']
I don't think importing anything is particularly elegant, or pythonic.
List comprehensions work, and I can't see a reason not to use them (or no good reason to import something to do the same thing):
>>> x = [3,5,7,0,1,4,2,6]
>>> y = ['a','b','c','d','e','f','g','h']
>>> nList = [y[i] for i in x]
>>> nList
['d', 'f', 'h', 'a', 'b', 'e', 'c', 'g']
The list comprehension is doing the following:
indexes = [3,5,7,0,1,4,2,6]
data = ['a','b','c','d','e','f','g','h']
nList = []
for index in indexes:
nList += [data[index]]
The comprehension looks pretty pythonic and elegant to me.
I would go with itemgetter but you could also map list.__getitem__:
l = ['a', 'b', 'c', 'd', 'e']
l_ids = [1, 2, 3]
new = list(map(l.__getitem__, l_ids))
If all the list elements are of the same type, it is possible to use numpy:
from numpy import *
new_list = array(l)[l_ids]
I am looking for some help comparing the order of 2 Python lists, list1 and list2, to detect when list2 is out of order.
list1 is static and contains the strings a,b,c,d,e,f,g,h,i,j. This is the "correct" order.
list2 contains the same strings, but the order and the number of strings may change. (e.g. a,b,f,d,e,g,c,h,i,j or a,b,c,d,e)
I am looking for an efficient way to detect when list2 is our of order by comparing it against list1.
For example, if list2 is a,c,d,e,g,i should return true (as the strings are in order)
While, if list2 is a,d,b,c,e should return false (as string d appears out of order)
First, let's define list1:
>>> list1='a,b,c,d,e,f,g,h,i,j'.split(',')
>>> list1
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
While your list1 happens to be in alphabetical order, we will not assume that. This code works regardless.
Now, let's create a list2 that is out-of-order:
>>> list2 = 'a,b,f,d,e,g,c,h,i,j'.split(',')
>>> list2
['a', 'b', 'f', 'd', 'e', 'g', 'c', 'h', 'i', 'j']
Here is how to test whether list2 is out of order or not:
>>> list2 == sorted(list2, key=lambda c: list1.index(c))
False
False means out-of-order.
Here is an example that is in order:
>>> list2 = 'a,b,d,e'.split(',')
>>> list2 == sorted(list2, key=lambda c: list1.index(c))
True
True means in-order.
Ignoring elements of list1 not in list2
Let's consider a list2 that has an element not in list1:
>>> list2 = 'a,b,d,d,e,z'.split(',')
To ignore the unwanted element, let's create list2b:
>>> list2b = [c for c in list2 if c in list1]
We can then test as before:
>>> list2b == sorted(list2b, key=lambda c: list1.index(c))
True
Alternative not using sorted
>>> list2b = ['a', 'b', 'd', 'd', 'e']
>>> indices = [list1.index(c) for c in list2b]
>>> all(c <= indices[i+1] for i, c in enumerate(indices[:-1]))
True
Why do you need to compare it to list1 since it seems like list1 is in alphabetical order? Can't you do the following?
def is_sorted(alist):
return alist == sorted(alist)
print is_sorted(['a','c','d','e','g','i'])
# True
print is_sorted(['a','d','b','c','e'])
# False
Here's a solution that runs in expected linear time. That isn't too important if list1 is always 10 elements and list2 isn't any longer, but with longer lists, solutions based on index will experience extreme slowdowns.
First, we preprocess list1 so we can quickly find the index of any element. (If we have multiple list2s, we can do this once and then use the preprocessed output to quickly determine whether multiple list2s are sorted):
list1_indices = {item: i for i, item in enumerate(list1)}
Then, we check whether each element of list2 has a lower index in list1 than the next element of list2:
is_sorted = all(list1_indices[x] < list1_indices[y] for x, y in zip(list2, list2[1:]))
We can do better with itertools.izip and itertools.islice to avoid materializing the whole zip list, letting us save a substantial amount of work if we detect that list2 is out of order early in the list:
# On Python 3, we can just use zip. islice is still needed, though.
from itertools import izip, islice
is_sorted = all(list1_indices[x] < list1_indices[y]
for x, y in izip(list2, islice(list2, 1, None)))
is_sorted = not any(list1.index(list2[i]) > list1.index(list2[i+1]) for i in range(len(list2)-1))
The function any returns true if any of the items in an iterable are true. I combined this with a generator expression that loops through all the values of list2 and makes sure they're in order according to list1.
if list2 == sorted(list2,key=lambda element:list1.index(element)):
print('sorted')
Let's assume that when you are writing that list1 is strings a,b,c,d,e,f,g,h,i that this means that a could be 'zebra' and string b could actually be 'elephant' so the order may not be alphabetical. Also, this approach will return false if an item is in list2 but not in list1.
good_list2 = ['a','c','d','e','g','i']
bad_list2 = ['a','d','b','c','e']
def verify(somelist):
list1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
while len(list1) > 0:
try:
list1 = list1[:list1.index(somelist.pop())]
except ValueError:
return False
return True
I want to make a loop for items in list that are not present in other_list, in one line. Something like this:
>>> list = ['a', 'b', 'c', 'd']
>>> other_list = ['a', 'd']
>>> for item in list not in other_list:
... print item
...
b
c
How is this possible?
for item in (i for i in my_list if i not in other_list):
print item
Its a bit more verbose, but its just as efficient, as it only renders each next element on the following loop.
Using set (which might do more than what you actually want to do) :
for item in set(list)-set(other_list):
print item
A third option: for i in filter(lambda x: x not in b, a): print(i)
list comprehension is your friend
>>> list = ['a', 'b', 'c', 'd']
>>> other_list = ['a', 'd']
>>> [x for x in list if x not in other_list]
['b', 'c']
also dont name things "list"
I have two lists (of different lengths). One changes throughout the program (list1), the other (longer) doesn't (list2). Basically I have a function that is supposed to compare the elements in both lists, and if an element in list1 is in list2, that element in a copy of list2 is changed to 'A', and all other elements in the copy are changed to 'B'. I can get it to work when there is only one element in list1. But for some reason if the list is longer, all the elements in list2 turn to B....
def newList(list1,list2):
newList= list2[:]
for i in range(len(list2)):
for element in list1:
if element==newList[i]:
newList[i]='A'
else:
newList[i]='B'
return newList
Try this:
newlist = ['A' if x in list1 else 'B' for x in list2]
Works for the following example, I hope I understood you correctly? If a value in B exists in A, insert 'A' otherwise insert 'B' into a new list?
>>> a = [1,2,3,4,5]
>>> b = [1,3,4,6]
>>> ['A' if x in a else 'B' for x in b]
['A', 'A', 'A', 'B']
It could be because instead of
newList: list2[:]
you should have
newList = list2[:]
Personally, I prefer the following syntax, which I find to be more explicit:
import copy
newList = copy.copy(list2) # or copy.deepcopy
Now, I'd imagine part of the problem here is also that you use the same name, newList, for both your function and a local variable. That's not so good.
def newList(changing_list, static_list):
temporary_list = static_list[:]
for index, content in enumerate(temporary_list):
if content in changing_list:
temporary_list[index] = 'A'
else:
temporary_list[index] = 'B'
return temporary_list
Note here that you have not made it clear what to do when there are multiple entries in list1 and list2 that match. My code marks all of the matching ones 'A'. Example:
>>> a = [1, 2, 3]
>>> b = [3,4,7,2,6,8,9,1]
>>> newList(a,b)
['A', 'B', 'B', 'A', 'B', 'B', 'B', 'A']
I think this is what you want to do and can put newLis = list2[:] instead of the below but prefer to use list in these cases:
def newList1(list1,list2):
newLis = list(list2)
for i in range(len(list2)):
if newLis[i] in list1:
newLis[i]='A'
else: newLis[i]='B'
return newLis
The answer when passed
newList1(range(5),range(10))
is:
['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B']
Is there a way to add multiple items to a list in a list comprehension per iteration? For example:
y = ['a', 'b', 'c', 'd']
x = [1,2,3]
return [x, a for a in y]
output: [[1,2,3], 'a', [1,2,3], 'b', [1,2,3], 'c', [1,2,3], 'd']
sure there is, but not with a plain list comprehension:
EDIT: Inspired by another answer:
y = ['a', 'b', 'c', 'd']
x = [1,2,3]
return sum([[x, a] for a in y],[])
How it works: sum will add a sequence of anythings, so long as there is a __add__ member to do the work. BUT, it starts of with an initial total of 0. You can't add 0 to a list, but you can give sum() another starting value. Here we use an empty list.
If, instead of needing an actual list, you wanted just a generator, you can use itertools.chain.from_iterable, which just strings a bunch of iterators into one long iterator.
from itertools import *
return chain.from_iterable((x,a) for a in y)
or an even more itertools friendly:
return itertools.chain.from_iterable(itertools.izip(itertools.repeat(x),y))
There are other ways, too, of course: To start with, we can improve Adam Rosenfield's answer by eliminating an unneeded lambda expression:
return reduce(list.__add__,([x, a] for a in y))
since list already has a member that does exactly what we need. We could achieve the same using map and side effects in list.extend:
l = []
map(l.extend,[[x, a] for a in y])
return l
Finally, lets go for a pure list comprehension that is as inelegant as possible:
return [ y[i/2] if i%2 else x for i in range(len(y)*2)]
Here's one way:
y = ['a', 'b', 'c', 'd']
x = [1,2,3]
return reduce(lambda a,b:a+b, [[x,a] for a in y])
x = [1,2,3]
y = ['a', 'b', 'c', 'd']
z = []
[z.extend([x, a]) for a in y]
(The correct value will be in z)