Re assign a list efficiently - python

This is a MWE of the re-arrainging I need to do:
a = [[1,2,3], [4,5,6], [7,8,9], [10,11,12]]
b = [[], [], []]
for item in a:
b[0].append(item[0])
b[1].append(item[1])
b[2].append(item[2])
which makes b lool like this:
b = [[1, 4, 7, 10], [2, 5, 8, 11], [3, 6, 9, 12]]
I.e., every first item in every list inside a will be stored in the first list in b and the same for lists two and three in b.
I need to apply this to a somewhat big a list, is there a more efficient way to do this?

There is a much better way to transpose your rows and columns:
b = zip(*a)
Demo:
>>> a = [[1,2,3], [4,5,6], [7,8,9], [10,11,12]]
>>> zip(*a)
[(1, 4, 7, 10), (2, 5, 8, 11), (3, 6, 9, 12)]
zip() takes multiple sequences as arguments and pairs up elements from each to form new lists. By passing in a with the * splat argument, we ask Python to expand a into separate arguments to zip().
Note that the output gives you a list of tuples; map elements back to lists as needed:
b = map(list, zip(*a))

Related

list index out of range, whenever I change the list size

Im trying to rotate a list of list 90 degrees. For example, change this:
[[1,2,3], [4,5,6], [7,8,9]]
to
[[7,4,1], [8,5,2],[9,6,3]]
Visually:
[[1,2,3], [[7,4,1],
[4,5,6], --> [8,5,2],
[7,8,9]] [9,6,3]]
Whenever I change the list size to be more elements or less it always says the index is out of range? What is going on?
def rotate(list1):
bigList = [] #create a list that we will append on to
for i in (range(len(list1)+1)): #loop through the list looking at the indexes
newList = []
for j in reversed(range(len(list1))): #reverse that list
newList.append(list1[j][i])
bigList.append((newList)) #append the elements to the bigList reversed
return bigList
What you are doing can be very easily done in a single line using reversed and zip . Actual issue in your code given below in this answer.
Example -
list(zip(*reversed(yourlist)))
You do not need the list(...) for Python 2.x, as zip() returns a list in Python 2.x .
Demo -
>>> list(zip(*reversed([[1,2,3], [4,5,6], [7,8,9]])))
[(7, 4, 1), (8, 5, 2), (9, 6, 3)]
>>> list(zip(*reversed([[1,2,3,4], [5,6,7,8], [9,10,11,12]])))
[(9, 5, 1), (10, 6, 2), (11, 7, 3), (12, 8, 4)]
If you want a list of lists, instead of list of tuple, you can use list comprehension (or map(list, zip(*reversed(....)))). Example -
[list(x) for x in zip(*reversed(yourlist))]
Demo -
>>> [list(x) for x in zip(*reversed([[1,2,3], [4,5,6], [7,8,9]]))]
[[7, 4, 1], [8, 5, 2], [9, 6, 3]]
>>> [list(x) for x in zip(*reversed([[1,2,3,4], [5,6,7,8], [9,10,11,12]]))]
[[9, 5, 1], [10, 6, 2], [11, 7, 3], [12, 8, 4]]
* is the syntax for unpacking , so the list returned by reversed() is unpacked into zip() and passed as separate arguments to it.
Then zip() function combines the elements of each of its argument at its corresponding index (like all first arguments together , all second arguments together, etc.) , Hence we get the result we need.
The actual issue for the original code occurred because of the following line -
for i in (range(len(list1)+1)):
You are looping till len(list1) + 1 , hence eventually you try to access elements like list1[0][len(list1)] , but that does not exist in your case.
Assuming that list1's sublist all will have same amount of elements, what you really need there would be len(list1[0]) . Example -
def rotate(list1):
bigList = [] #create a list that we will append on to
for i in (range(len(list1[0]))): #loop through the list looking at the indexes
newList = []
for j in reversed(range(len(list1))): #reverse that list
newList.append(list1[j][i])
bigList.append((newList)) #append the elements to the bigList reversed
return bigList
Demo -
>>> def rotate(list1):
... bigList = [] #create a list that we will append on to
... for i in (range(len(list1[0]))): #loop through the list looking at the indexes
... newList = []
... for j in reversed(range(len(list1))): #reverse that list
... newList.append(list1[j][i])
... bigList.append((newList)) #append the elements to the bigList reversed
... return bigList
...
>>> rotate([[1,2,3], [4,5,6], [7,8,9]])
[[7, 4, 1], [8, 5, 2], [9, 6, 3]]
>>> rotate([[1,2,3,4], [5,6,7,8], [9,10,11,12]])
[[9, 5, 1], [10, 6, 2], [11, 7, 3], [12, 8, 4]]
Change
for i in (range(len(list1)+1))
to
for i in (range(len(list1)))
and it should work
If you change the for i line to:
for i in (range(len(list1))):
then it gives the expected result.
Note that your code only works for n-by-n lists and not for n-by-m lists
Typical example of an off by one error ;-)

Iterating through nested list

How do i create a function to iterate my list in this manner.
Seems simply but im stuck...
myList= [[1,2,3], [4,5,6], [7,8,9]]
def name(myList):
somework..
newList = [[1,4,7]. [ 2,5,8], [3,6,9]]
In [3]: zip(*myList)
Out[3]: [(1, 4, 7), (2, 5, 8), (3, 6, 9)]
if you specifically want list
In [4]: [list(x) for x in zip(*myList)]
Out[4]: [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
for more details on zip function look at this
zip is what you want + argument unpacking. It's awesome. I like to think of it as python's builtin transpose.
newList = zip(*myList)
This will actually give you an iterable (python3.x) or list (python2.x) of tuple, but that's good enough for most purposes.

In Python, how can I create lists that contain a certain index of other lists?

Say I have several lists
A = [1,2,3]
B = [4,5,6]
C = [7,8,9]
How can I create new lists so that they contain matching indexes, as in:
D = [1,4,7]
E = [2,5,8]
F = [3,6,9]
The original lists will always contain the same number of elements, and I need this to work for any number of elements and any number of lists, not just three. I figure I need to loop over a range, but I'm not sure how to go about it.
If I understand you correctly, you may be looking for zip():
>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> c = [7, 8, 9]
>>> zipped = zip(a, b, c)
>>> zipped
[(1, 4, 7), (2, 5, 8), (3, 6, 9)]
If instead of several lists, you have a list of the lists that you want to zip, then you can use * for unpacking the sublists, as follows:
>>> myListOfLists = [[1,2,3],[4,5,6],[7,8,9]]
>>> zipped = zip(*myListOfLists)
>>> zipped
[(1, 4, 7), (2, 5, 8), (3, 6, 9)]

Grouping Elements of Lists Within a List by Index

I am trying to take a list of lists, and return a list of lists which contain each element at an index of the original list of lists. I know that that's badly worded. Here's an example.
Say I have the following list of lists:
[[1,2,3], [4,5,6], [7,8,9]]
I want to get another list of lists, in which each list is a list of each elements at a specific index. For example:
[[1,2,3], [4,5,6], [7,8,9]] becomes [[1,4,7], [2,5,8], [3,6,9]]
So the first list in the returned list, contains all of the elements at the first index of each of the original list, and so on. I'm stuck, and have no idea how this could be done. Any help would be appreciated. Thanks.
>>> [list(t) for t in zip(*[[1,2,3], [4,5,6], [7,8,9]])]
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]
perhaps an easy way wiould be:
a=[[1,2,3], [4,5,6], [7,8,9]]
b=zip(*a)
b will be equal to [(1, 4, 7), (2, 5, 8), (3, 6, 9)].
hopes this helps
Dan D's answer is correct and will work in Python 2.x and Python 3.x.
If you're doing lots of matrix operations, not just transposition, it's worth considering Numpy at this juncture:
>>> import numpy as np
>>> x = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> np.swapaxes(x, 0, 1)
array([[1, 4, 7],
[2, 5, 8],
[3, 6, 9]])
Or, more simply, as per the comments, use numpy.transpose():
>>> np.transpose(x)
array([[1, 4, 7],
[2, 5, 8],
[3, 6, 9]])
In addition to the zip(*lists) approach, you could also use a list comprehension:
[[l[i] for l in lists] for i in range(3)]
In case you want to simply transpose your matrix -e.g. to get new matrix where rows are cols from initial matrix while columns equal to the initial matrix rows values then you can use:
initMatr = [
[1,2,3],
[4,5,6],
[7,8,9]
]
map(list, zip(*initMatr))
>>> [
[1,4,7],
[2,5,8],
[3,6,9]
]
OR in case you want to rotate matrix left then:
map(list, zip(*map(lambda x: x[::-1], initMatr)
>>> [
[3,6,9],
[2,5,8],
[1,4,7]
]

array except other

I have 2 arrays:
arr1 = [a,b,c,d,e]
arr2 = [c,d,e]
I want to give array arr1 except arr2.
Mathematically, you're looking for a difference between two sets represented in lists. So how about using the Python set, which has a builtin difference operation (overloaded on the - operator)?
>>>
>>> arr = [1, 2, 3, 4, 5]
>>> arr2 = [3, 4, 9]
>>> set(arr) - set(arr2)
>>> sdiff = set(arr) - set(arr2)
>>> sdiff
set([1, 2, 5])
>>> list(sdiff)
[1, 2, 5]
>>>
It would be more convenient to have your information in a set in the first place, though. This operation suggests that a set better fits your application semantics than a list. On the other hand, if you may have duplicates in the lists, then set is not a good solution.
So you want the difference of two lists:
list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
list2 = [1, 2, 3, 4, 4, 6, 7, 8, 11, 77]
def list_difference(list1, list2):
"""uses list1 as the reference, returns list of items not in list2"""
diff_list = []
for item in list1:
if not item in list2:
diff_list.append(item)
return diff_list
print list_difference(list1, list2) # [5, 9, 10]
Or using list comprehension:
# simpler using list comprehension
diff_list = [item for item in list1 if item not in list2]
print diff_list # [5, 9, 10]
If you care about (1) preserving the order in which the items appear and (2) efficiency in the case where your lists are large, you probably want a hybrid of the two solutions already proposed.
list2_items = set(list2)
[x for x in list1 if x not in list2_items]
(Converting both to sets will lose the ordering. Using if x not in list2 in your list comprehension will give you in effect an iteration over both lists, which will be inefficient if list2 is large.)
If you know that list2 is not very long and don't need to save every possible microsecond, you should probably go with the simple list comprehension proposed by Flavius: it's short, simple and says exactly what you mean.

Categories

Resources