When I define a list and try to change a single item like this:
list_of_lists = [['a', 'a', 'a'], ['a', 'a', 'a'], ['a', 'a', 'a']]
list_of_lists[1][1] = 'b'
for row in list_of_lists:
print row
It works as intended. But when I try to use list comprehension to create the list:
row = ['a' for range in xrange(3)]
list_of_lists = [row for range in xrange(3)]
list_of_lists[1][1] = 'b'
for row in list_of_lists:
print row
It results in an entire column of items in the list being changed. Why is this? How can I achieve the desired effect with list comprehension?
Think about if you do this:
>>> row = ['a' for range in xrange(3)]
>>> row2 = row
>>> row2[0] = 'b'
>>> row
['b', 'a', 'a']
This happens because row and row2 are two different names for the same list (you have row is row2) - your example with nested lists only obscures this a little.
To make them different lists, you can cause it to re-run the list-creation code each time instead of doing a variable assignment:
list_of_lists = [['a' for range in xrange(3)] for _ in xrange(3)]
or, create a new list each time by using a slice of the full old list:
list_of_lists = [row[:] for range in xrange(3)]
Although this isn't guaranteed to work in general for all sequences - it just happens that list slicing makes a new list for the slice. This doesn't happen for, eg, numpy arrays - a slice in those is a view of part of the array rather than a copy. If you need to work more generally than just lists, use the copy module:
from copy import copy
list_of_lists = [copy(row) for range in xrange(3)]
Also, note that range isn't the best name for a variable, since it shadows the builtin - for a throwaway like this, _ is reasonably common.
This happens because most objects in python (exept for strings and numbers) get passed reference (not exactly by reference, but here you have a better explanation) so when you try to do it in the "list comprehensive" way, yo get a list of 3 references to the same list (the one you called "row"). So when you change the value of one row you see that change in all of them)
So what you have to do is to change your "matrix" creation like this:
list_of_lists = [list(row) for range in xrange(3)]
Here you have some ideas on how to correctly get a copy of a list. Depending on what you are trying to do, you may use one or another...
Hope it helps!
Copy the list instead of just the reference.
list_of_lists = [row[:] for range in xrange(3)]
Related
I am trying to find a way to use a list of indexes to set values at multiple places in a list (as is possible with numpy arrays).
I found that I can map __getitem__ and a list of indexes to return the values at those indexes:
# something like
a_list = ['a', 'b', 'c']
idxs = [0, 1]
get_map = map(a_list.__getitem__, idxs)
print(list(get_map)) # yields ['a', 'b']
However, when applying this same line of thought to __setitem__, the setting fails. This probably has something to do with pass-by-reference vs pass-by-value, which I have never fully understood no matter how many times I've read about it.
Is there a way to do this?
b_list = ['a', 'b', 'c']
idxs = [0, 1]
put_map = map(b_list.__setitem__, idx, ['YAY', 'YAY'])
print(b_list) # yields ['YAY', 'YAY', 'c']
For my use case, I only want to set one value at multiple locations. Not multiple values at multiple locations.
EDIT: I know how to use list comprehension. I am trying to mimic numpy's capability to accept a list of indexes for both getting and setting items in an array, except for lists.
The difference between the get and set case is that in the get case you are interested in the result of map itself, but in the set case you want a side effect. Thus, you never consume the map generator and the instructions are never actually executed. Once you do, b_list gets changed as expected.
>>> put_map = map(b_list.__setitem__, idxs, ['YAY', 'YAY'])
>>> b_list
['a', 'b', 'c']
>>> list(put_map)
[None, None]
>>> b_list
['YAY', 'YAY', 'c']
Having said that, the proper way for get would be a list comprehension and for set a simple for loop. That also has the advantage that you do not have to repeat the value to put in place n times.
>>> for i in idxs: b_list[i] = "YAY"
>>> [b_list[i] for i in idxs]
['YAY', 'YAY']
This question already has answers here:
Printing a column of a 2-D List in Python
(7 answers)
Closed 6 years ago.
If I have a 2D list in python Data and I want to create a slice of that 2D list, where I select all the elements from the first index and a single on from the second.
eg.
Data = [[a,b,c],[d,e,f],[h,i,g]]
and I want the list;
raw_data = [b,e,i]
Why does doing something like;
raw_data = Data[:][1]
not give the desired output?
I have specified the whole first index and the 1 index for the second.
Instead I get the output that is;
raw_data = [d,e,f]
Which is what I would expect to get from;
raw_data = Data[1][:]
raw_data = [d,e,f]
So;
Data[1][:] = Data[:][1]
Which is not compatible with my mental model of how lists work in python.
Instead I have to use a loop to do it;
raw_data = []
for i in xrange(0,len(Data),1):
raw_data.append(Data[i][1])
So my question is, can anyone explain why Data[1][:] = Data[:][1] ?
Thanks for reading!
lst[:] has no explicit start an no explicit end, so according to the Python documentation, it will return a copy of the list starting at the start and ending at the end of the list. In other words, it will return a copy of same list you have before. So:
>>> Data = [['a','b','c'],['d','e','f'],['h','i','g']]
>>> Data[:]
[['a', 'b', 'c'], ['d', 'e', 'f'], ['h', 'i', 'g']]
So when you say Data[:], that will evaluate to the same as a copy of Data, meaning that Data[:][1] essentially is just Data[1], which is [d,e,f]
If you do it the other way:
>>> Data[1]
['d', 'e', 'f']
>>> Data[1][:]
['d', 'e', 'f']
You get the second element in data, [d,e,f], then you use that same list slicing syntax as before to get that same list again.
To get what you want, I'd use a list comprehension:
>>> [x[1] for x in Data]
['b', 'e', 'i']
Simple as that.
Vanilla Python doesn't have two dimensional arrays, but it does allow for extensions to implement them. You have a list of lists, which is somewhat different.
The solution to your problem is to use numpy which does have a 2d array type. You can then say data[:,1]
Why your example doesn't work as you expect: data[:] means "a copy of data" and so data[:][1] means the index 1 element of the copy of data, which is [d,e,f]
It's pretty obvious if you go through what's happening from left to right.
raw_data = Data[:]
will give you the entirety of Data, so the whole list of lists: [[a,b,c],[d,e,f],[h,i,g]]
raw_data = Data[:][1]
will then give you the element at index 1 in this list, which is [d,e,f].
On the other hand,
raw_data = Data[1]
will return the element at position 1 in data, which is also [d,e,f].
[:] on this object will again return itself in its entirety.
What you are trying to do is best done with a list comprehension, such as:
raw_data = [x[1] for x in Data]
This will give you list of all second elements in all lists in Data.
I have to make list2 which use name from list1 but number of name in list1 can vary.
foo = ['spam', 'eggs']
bar = [['spam', ['a', 'b']], ['eggs', ['c', 'd']]]
So, I reserve bar by
bar = [[None] * 2] * len(foo)
and copy names from list1 by looping
bar[i][0] = foo[i]
but the result is the name in every sublist are the same like this
bar = [['eggs', ['a', 'b']], ['eggs', ['c', 'd']]]
I try to reserve by
bar = [[None, None], [None, None]]
and no issue at all. So I think the problem come from how I reserve the list.
How can I fix this. If you don't understand my English, please ask. Thanks.
Create your second list like this:
list2 = [[None]*2 for x in range(len(list1))]
or alternativly, if for some reason you don't like to use comprehensions, you could do
list2 = []
for x in range(len(list1)):
list2.append([None,None])
The problem is that when you do something like
[listItem] * numCopies
you get a list that contains numCopies copies of the same listItem, so your sublists are all the same - when you change one you change them all.
The way that I have suggested will create unique lists that contain the same content (two copies of None). Thus changing one of these sublists will not change any others.
Python list type is for contiguous sequences, not arrays. (Python does have an array type, but list is not that.)
The example data structure you show (a sequence of pairs, with a single name and a collection of values) would be IMO better served by a mapping. Python's built-in mapping type is dict.
foo = ['spam', 'eggs']
bar = {
'spam': ['a', 'b'],
'eggs': ['c', 'd'],
}
So there is no need to reserve space in a Python list nor a Python dict. Just assign and delete items as you need, and the collection will manage its own space.
When you want to set items in the mapping bar keyed by the values you already have in the list foo, you just use a value from the list as a key in the dict:
for name in foo:
value = determine_what_is_the_value_for(name)
bar[name] = value
for example if I had
array=[["A",1],["B",2],["C",1]]
is there any way I can find ["A",1] by just looking for "A"? I'm trying to use this in a situation where the first thing in the array is unique so there's no point in looking at the second, also I have no way of knowing what the second variable is
Iterate over items present inside the outer list and check that the first element of inner list satisfies a particular condition.
>>> a=[["A",1],["B",2],["C",1]]
>>> next(i for i in a if i[0] == 'A')
['A', 1]
>>> [i for i in a if i[0] == 'A']
[['A', 1]]
If you're in control of the datatype, depending on whate else you're doing with this object, a dictionary may be a better choice for this:
Rather than
array=[["A",1],["B",2],["C",1]]
use
d={"A":1, "B":2, "C":1}
Then you can access the element associated with "A" simply with
>> d["A"]
1
If you want to transform your list into a dictionary:
d = dict(array)
Suppose I have
lists = ["ABC","AC","CCCC","BC"]
I want a new list where items in my new list are grouped by position based on lists meaning for each string in the list take the position 0("ABC" position 0 is "A") and make a string out of it.
position = ["AACB","BCCC","CC","C"]
I try:
for i in range(0,4):
want = [lists[i] for stuff in lists]
and I get
IndexError: string index out of range
Which makes sense because all the strings are different size. Can anyone help?
I think you might want this:
import itertools
lists = ["ABC","AC","CCCC","BC"]
position = map(''.join,itertools.izip_longest(*lists, fillvalue=''))
and you get:
['AACB', 'BCCC', 'CC', 'C']
edit: now with the new example...
You can use this list comprehension:
>>> lists = ["ABC","AC","CCCC","BC"]
>>> [''.join([s[i:i+1] for s in lists]) for i, el in enumerate(lists)]
['AACB', 'BCCC', 'CC', 'C']
Using the slice notation prevents index errors on non-existing elements.