Multidimensional slicing in raw Python - python

Let's say I have an array defined as
[
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
and I wanted to get a vertical slice, returning [2, 5, 8]. Is there any way to do this with slicing syntax in straight Python? When I try to look up multidimensional slicing, the only results I tend to see are all talking about numpy, and using the syntax discussed there gives errors in raw Python.

I don't know of a direct way to get a vertical slice and this is probably more expensive than the list comprehension answer but still
z = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
list(zip(*z)[1])
[2, 5, 8]
Or this may be slightly less expensive
from itertools import izip, islice
list(*islice(izip(*z), 1, 2))
[2, 5, 8]

Sure. You could generally use a for loop. Or simply you could code like:
>>> test = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
>>> [row[1] for row in test]
[2, 5, 8]
This piece of code will generate a new list for a vertical slice. If you really need a slice operation, the following code would be useful:
>>> [item for sublist in test for item in sublist][1::len(test[0])]
[2, 5, 8]

Related

How to merge a multidimensional list into one list of tuples?

Suppose we have the following multi-dimensional list :
import numpy as np
lst = [[1,2,3], [4,5,6],[7,8,9]]
Expected output :
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]
My problem is that the list 'lst' varies in size, otherwise I would achieve the wanted result with one for loop. I tried generalizing the task, here goes my attempt :
lst = [[1,2,3], [4,5,6],[7,8,9]]
test = np.array(list(zip(lst[i] for i in range(len(lst)))))
Clearly, this isn't the result I wanted. Is there a straight forward way of doing so without iterating over each element in the list ?
You can use * to "unpack" lst:
lst = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
test = list(map(list, zip(*lst)))
print(test)
Prints:
[[1, 4, 7],
[2, 5, 8],
[3, 6, 9]]
Using numpy.transpose instead
import numpy as np
result = np.transpose(np.array(lst))
or simply np.array(lst).T
Using Numpy swapaxes
arr = np.array(lst)
arr=np.swapaxes(arr,1,0)

Why does list index -0 form an empty list?

Imagine a list:
a = [1, 2, 3, 4, 5, 6, 7, 8]
where you simply want to iterate and print slices from this list by:
for i in range(len(a)-2):
print(a[2: -i])
This yields:
[]
[3, 4, 5, 6, 7]
[3, 4, 5, 6]
[3, 4, 5]
[3, 4]
[3]
Here, I note that specifying a list index as a[2: -0] returns an empty list.
I would have thought this would have returned all the numbers from index 2 upwards.
Is there a simple explanation for this behaviour?
Returning to the example where the desired result would be:
[3, 4, 5, 6, 7, 8]
[3, 4, 5, 6, 7]
[3, 4, 5, 6]
[3, 4, 5]
[3, 4]
[3]
The introduction of an if statement seems like a clumsy way to treat this problem. Such as:
for i in range(len(a)-2):
if i == 0:
print(a[2:])
else:
print(a[2:-i])
Is there a better way?
for i in range(len(a)-2):
print(a[2: -i or None])
This can make it look neat. But as suggested by #Akshat2249 a[2:0] should return nothing.
There are a few ways to work around the issue, that -0 is 0, which puts it at the wrong end of the list for your intended slicing.
One option is as Vishal Dhawan has proposed, to substitute None for the -0 value. That works because None in a slice means "keep slicing until the end of the list".
Another option would be to do the math yourself for the indexes, rather than using negative indexes to count from the end. You'd want a[2:len(a)-i].
The best option is to change the range you're looping over so that it directly gives you the slice-end index you want, without any additional math being needed. Danil Melnikov's answer almost does this, but it has some errors with its range call, so it produces the wrong output. Try:
for i in range(len(a), 2, -1):
print(a[2: i])
a = [1, 2, 3, 4, 5, 6, 7, 8]
for i in range(len(a)-1, 0, -1):
print(a[2: i])
Would give:
[3, 4, 5, 6, 7, 8]
[3, 4, 5, 6, 7]
[3, 4, 5, 6]
[3, 4, 5]
[3, 4]
[3]
When you work with list slice it has logic like start from 2 and reach 0, but if start point > finish point the loop is over, we can specify the step, -1 or +1 [2:0:-1]

Met with "Index Out of Range" when Trying to Transpose Matrix

I'm trying to figure out how to transpose a matrix with vectors that contain an unequal amount of elements.
I'm just learning to program and I'm currently working through the Python Tutorial and I'm stuck on an example listed in "Nested List Comprehensions" here: https://docs.python.org/3/tutorial/datastructures.html#nested-list-comprehensions.
Here is a very slight variation of example shown in the Python Tutorial:
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
[[row[i] for row in matrix] for i in range(3)]
I decided to create my own solution for the example because I wanted the code to be a bit more dynamic, which was this:
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
[[row[i] for row in matrix] for i in range(len(matrix))]
However, shortly after coming up with my solution I noticed there was a lot of ways it could still break and tried figuring out a solution that wouldn't break under any of the following scenarios:
# Scenario 1: Vectors of unequal length sizes.
matrix = [
[1, 2, 3, 4],
[5, 6, 7],
[8, 9, 10, 11]
# Scenario 2: len(vector) > len(matrix)
matrix = [
[1, 2, 3, 4],
[5, 6, 7, 8]
]
# Scenario 3: len(vector) < len(matrix)
matrix = [
[1, 2],
[3, 4],
[5, 6],
[7, 8]
]
If anyone can come up with a solution using nested list comprehensions that would be able to handle all 3 of these scenarios, I would greatly appreciate your help.
disclaimer: all of this assumes your rows are all of the same length (as should be for a matrix).
just a minor tweak will do:
[[row[i] for row in matrix] for i in range(len(matrix[0]))]
the point is to have i in the range of the length of the rows of you matrix len(matrix[0]) (i am using the first row here as it always should exist).
a more elegant way is using zip:
list(zip(*matrix))
although you will get tuples as rows. if you need lists you can do:
[list(row) for row in zip(*matrix1)]
This works for both equal and unequal row lengths by collapsing the sparse rows to the left:
[[row[i] for row in matrix if i < len(row)] for i in range(max(len(r) for r in matrix))]
So scenario 1
matrix = [
[1, 2, 3, 4],
[5, 6, 7],
[8, 9, 10, 11]]
becomes
[[1, 5, 8],
[2, 6, 9],
[3, 7, 10],
[4, 11]]

Create and fill each list within a list

I learned to create and manage nested lists by building a NxM matrix and filling it with random integers.
I've solved it via a simple one-liner and struggled to write a comprehensive solution.
I call the first solution as a cheating because I got this tip from this website, but didn't completely understand how it works. So, I've tried to write a more detailed piece of code on my own, which was difficult, and I'm not completely sure I got it right.
The original solution:
from random import randint
size_n = int(input("Number of lists N "))
size_m = int(input("Number of elements M "))
matrix = [[randint(1,9) for m in range(size_m)] for n in range(size_n)]
print(matrix)
I had a hard time to create a list of list with the correct levels of loops in loops. I end up with the temporary matrix, but I'm not sure it's good solution.
The final solution:
from random import randint
size_n = int(input("Number of lists N "))
size_m = int(input("Number of elements M "))
matrix = []
for n in range(size_n):
tmp = []
for m in range(size_m):
tmp.append(randint(1,9))
matrix.append(tmp)
print(matrix)
Can you help me to understand what is the correct solution for the task?
P.S. Is it normal for developer to look for another solution if the code just works but you think it might be prettier?
matrix = [[randint(1,9) for m in range(size_m)] for n in range(size_n)]
Let's say our size_n is 5, for an example. Then range(size_n) gives
[0, 1, 2, 3, 4]
E. g. a list of 5 consecutive integers starting with 0. If you do something for each element of the list, that creates another list of the same size, for example:
>>>[88 for n in range(size_n)]
[88, 88, 88, 88, 88]
You are, of course, not limited by simple numbers, you can create, basically, anything. For example, empty lists:
>>>[[] for n in range(size_n)]
[[], [], [], [], []]
Then, of course, the inner lists don't have to be empty, you can populate them with numbers:
>>>[[1, 2, 3] for n in range(size_n)]
[[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]
Or you can use another range expression inside:
>>> [[range(1, 4)] for n in range(5)]
[[[1, 2, 3]], [[1, 2, 3]], [[1, 2, 3]], [[1, 2, 3]], [[1, 2, 3]]]
And you can use list comprehension on those inner lists just the same way:
>>> [[randint(1, 9) for m in range(1, 4)] for n in range(5)]
[[8, 4, 3], [6, 7, 2], [6, 8, 2], [9, 6, 1], [9, 1, 2]]
You can go deeper and deeper until you're bored or out of memory:
>>> [[[randint(1, 9) for n in range(1, 5)] for n in range(1, 4)] for n in range(5)]
[[[2, 9, 6, 1], [6, 7, 4, 5], [5, 9, 1, 7]], [[5, 2, 9, 3], [1, 8, 9, 7], [8, 4, 4, 8]], [[4, 4, 7, 9], [7, 1, 4, 2], [7, 8, 7, 3]], [[4, 4, 9, 9], [8, 8, 9, 5], [6, 1, 3, 9]], [[5, 9, 3, 2], [7, 5, 4, 7], [7, 7, 4, 3]]]
Nested list comprehension is usually the right solution, they are easy to read once you get used to the notation. Nested loops are OK too.
There's nothing wrong with looking for better ways to re-write your working code (I'd say, rather, it's wrong not to look), unless you develop an unhealthy obsession with it.
For Python 3 range(5) actually doesn't give [0, 1, 2, 3, 4] directly, but for this task it's effectively the same thing.

Special type of combination using itertools

I am almost finished with a task someone gave me that at first involved easy use of the product() function from itertools.
However, the person asked that it should also do something a bit different like:
li =
[[1, 2, 3],
[4, 5, 6]]
A regular product() would give something like: [1, 4], [1, 5], [1, 6], [2, 4], [2, 5], [2, 6], [3, 4] ...
What it should do is:
Do a regular product(), then, add the next item from the first element in the list and so on. A complete set of example would be:
[[1, 4, 2]
[1, 4, 3],
[1, 5, 2],
[1, 5, 3],
[2, 4, 3],
[2, 5, 3],
[2, 6, 3]]
How should I use itertools in this circumstance?
EDIT:
It might help if I explain the goal of the program:
The user will enter, for example, a 5 row by 6 column list of numbers.
A normal product() will result in a 5-number combination. The person wants a 6-number combination. Where will this "6th" number come from? It would come from his choice of which row he wants.
I wondering what is the magical computations you performing, but it look's like that's your formula:
k = int(raw_input('From What row items should be appeared again at the end?'))
res = [l for l in product(*(li+[li[k]])) if l[k]<l[len(li)] ]
Generalized for more than two sublist (map function would be the other alternative)
from pprint import pprint
for li in ([[1, 2, 3],
[4, 5, 6]],
[[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]]
):
triples= []
prevlist=li[0]
for nextlist in li[1:]:
for spacing in range(1,len(prevlist)):
triples.extend([[first,other,second]
for first,second in zip(prevlist,prevlist[spacing:])
for other in nextlist])
pprint(sorted(triples))

Categories