Python 3: Get diagonal elements of matrix (list of lists) about a point in that matrix without NumPy
A bit confusing, but I think this does it:
def getDiagonals(matrix, pos):
row, col = pos
nrows = len(matrix)
ncols = len(matrix[0]) if nrows > 0 else 0
# First diagonal
d1_i, d1_j = nrows - 1 - max(row - col, 0), max(col - row, 0)
d1_len = min(d1_i + 1, ncols - d1_j)
diag1 = [matrix[d1_i - k][d1_j + k] for k in range(d1_len)]
# Second diagonal
t = min(row, ncols - col - 1)
d2_i, d2_j = nrows - 1 - row + t, col + t
d2_len = min(d2_i, d2_j) + 1
diag2 = [matrix[d2_i - k][d2_j - k] for k in range(d2_len)]
return (diag1, diag2)
# Test
matrix = [[0, 0, 0, 0, 5],
[0, 0, 0, 4, 0],
[2, 0, 3, 0, 0],
[3, 2, 0, 2, 0],
[1, 0, 2, 0, 1]]
diagonals = getDiagonals(matrix, (1, 1))
print(diagonals[0])
# [1, 2, 3, 4, 5]
print(diagonals[1])
# [2, 2, 2]
diagonals = getDiagonals(matrix, (1, 3))
print(diagonals[0])
# [2, 2, 0]
print(diagonals[1])
# [1, 2, 3, 0, 0]
diagonals = getDiagonals(matrix, (2, 2))
print(diagonals[0])
# [1, 2, 3, 4, 5]
print(diagonals[1])
# [1, 2, 3, 0, 0]
Get all the diagonals in a matrix/list of lists in Python
There are probably better ways to do it in numpy than below, but I'm not too familiar with it yet:
import numpy as np
matrix = np.array(
[[-2, 5, 3, 2],
[ 9, -6, 5, 1],
[ 3, 2, 7, 3],
[-1, 8, -4, 8]])
diags = [matrix[::-1,:].diagonal(i) for i in range(-3,4)]
diags.extend(matrix.diagonal(i) for i in range(3,-4,-1))
print [n.tolist() for n in diags]
Output
[[-2], [9, 5], [3, -6, 3], [-1, 2, 5, 2], [8, 7, 1], [-4, 3], [8], [2], [3, 1], [5, 5, 3], [-2, -6, 7, 8], [9, 2, -4], [3, 8], [-1]]
Edit: Updated to generalize for any matrix size.
import numpy as np
# Alter dimensions as needed
x,y = 3,4
# create a default array of specified dimensions
a = np.arange(x*y).reshape(x,y)
print a
print
# a.diagonal returns the top-left-to-lower-right diagonal "i"
# according to this diagram:
#
# 0 1 2 3 4 ...
# -1 0 1 2 3
# -2 -1 0 1 2
# -3 -2 -1 0 1
# :
#
# You wanted lower-left-to-upper-right and upper-left-to-lower-right diagonals.
#
# The syntax a[slice,slice] returns a new array with elements from the sliced ranges,
# where "slice" is Python's [start[:stop[:step]] format.
# "::-1" returns the rows in reverse. ":" returns the columns as is,
# effectively vertically mirroring the original array so the wanted diagonals are
# lower-right-to-uppper-left.
#
# Then a list comprehension is used to collect all the diagonals. The range
# is -x+1 to y (exclusive of y), so for a matrix like the example above
# (x,y) = (4,5) = -3 to 4.
diags = [a[::-1,:].diagonal(i) for i in range(-a.shape[0]+1,a.shape[1])]
# Now back to the original array to get the upper-left-to-lower-right diagonals,
# starting from the right, so the range needed for shape (x,y) was y-1 to -x+1 descending.
diags.extend(a.diagonal(i) for i in range(a.shape[1]-1,-a.shape[0],-1))
# Another list comp to convert back to Python lists from numpy arrays,
# so it prints what you requested.
print [n.tolist() for n in diags]
Output
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[0], [4, 1], [8, 5, 2], [9, 6, 3], [10, 7], [11], [3], [2, 7], [1, 6, 11], [0, 5, 10], [4, 9], [8]]
How to get all the diagonal two-dimensional list without using numpy?
Try this. Define variables COL and ROW and then run the following function with your matrix.
def diagonalOrder(matrix) :
# There will be ROW+COL-1 lines in the output
for line in range(1, (ROW + COL)) :
# Get column index of the first element
# in this line of output. The index is 0
# for first ROW lines and line - ROW for
# remaining lines
start_col = max(0, line - ROW)
# Get count of elements in this line.
# The count of elements is equal to
# minimum of line number, COL-start_col and ROW
count = min(line, (COL - start_col), ROW)
# Print elements of this line
for j in range(0, count) :
print(matrix[min(ROW, line) - j - 1]
[start_col + j], end = "\t")
print()
Finding all elements below the big diagonal of a matrix
This is one way you can achieve that result:
matrix = [['a', 'p', 'p', 'l', 'e'],
['a', 'g', 'o', 'd', 'o'],
['n', 'n', 'e', 'r', 't'],
['g', 'a', 'T', 'A', 'C'],
['m', 'i', 'c', 's', 'r'],
['P', 'o', 'P', 'o', 'P']]
my_list = []
for f in range(1, len(matrix[0])):
s = []
for k in range(len(matrix[0]) - f):
s.append(matrix[len(matrix) - k - 1][f + k])
my_list.append(''.join(s))
print(my_list)
# ['ocAt', 'PsC', 'or', 'P']
Or using comprehension:
my_list = [''.join(matrix[len(matrix) - i - 1][j + i] for i in range(len(matrix[0]) - j))
for j in range(1, len(matrix[0]))]
To produce every substring in each diagonal you can do:
my_list = []
for j in range(1, len(matrix[0])):
for i1 in range(0, len(matrix[0]) - j):
for i2 in range(i1 + 1, len(matrix[0]) - j + 1):
s = []
for i in range(i1, i2):
s.append(matrix[len(matrix) - i - 1][j + i])
my_list.append(''.join(s))
print(my_list)
# ['o', 'oc', 'ocA', 'ocAt', 'c', 'cA', 'cAt', 'A', 'At', 't', 'P', 'Ps', 'PsC', 's', 'sC', 'C', 'o', 'or', 'r', 'P']
Or equivalently:
my_list = [''.join(matrix[len(matrix) - i - 1][j + i] for i in range(i1, i2))
for j in range(1, len(matrix[0]))
for i1 in range(0, len(matrix[0]) - j)
for i2 in range(i1 + 1, len(matrix[0]) - j + 1)]
--
One solution for substrings in upper diagonals:
my_list = []
for i in range(len(matrix)):
for j1 in range(min(i + 1, len(matrix[0]))):
for j2 in range(j1, min(i + 1, len(matrix[0]))):
s = []
for j in range(j1, j2 + 1):
s.append(matrix[i - j][j])
my_list.append(''.join(s))
print(my_list)
# ['a', 'a', 'ap', 'p', 'n', 'ng', 'ngp', 'g', 'gp', 'p', 'g',
# 'gn', 'gno', 'gnol', 'n', 'no', 'nol', 'o', 'ol', 'l', 'm',
# 'ma', 'mae', 'maed', 'maede', 'a', 'ae', 'aed', 'aede', 'e',
# 'ed', 'ede', 'd', 'de', 'e', 'P', 'Pi', 'PiT', 'PiTr', 'PiTro',
# 'i', 'iT', 'iTr', 'iTro', 'T', 'Tr', 'Tro', 'r', 'ro', 'o']
Find diagonals in a Python matrix using lists
If you consider the logic of your 'right-to-left' loop, you are actually just doing the same as your 'left-to-right' loop in reverse order. To really get the 'right-to-left' pass right you have to have your i
and j
indices moving in different directions.
So your conditional statement in this section should look like:
if i-3>=0 and j+3<7:
if (llista[i][j]==1 and llista[i-1][j+1]==1 and llista[i-2][j+2]==1 and llista[i-3][j+3]==1) or (llista[i][j]==2 and llista[i-1][j+1]==2 and llista[i-2][j+2]==2 and llista[i-3][j+3]==2 ):
print("There is at least one right to left diagonal")
There are a ton of optimizations you could use by importing libraries like numpy
or itertools
as AResem showed. That answer is not entirely correct though for the following reason.
When you say return True, k
you are not exercising any control over the value of k
because it was used in the list comprehension just above and will just have the value of the last item it iterated over. So when your function finds a diagonal it reports the wrong number about two thirds of the time.
Here is an edited function that gives the correct results:
def check_diagonals(matrix):
for offset in range(-2, 4):
diag = matrix.diagonal(offset=offset)
# Here you can create a tuple of numbers with the number of times they are repeated.
# This allows you to keep your k and g values associated.
repeat_groups = [(k, sum(1 for _ in g)) for k, g in groupby(diag)]
# By using the built-in max function with the 'key' keyword, you can find
# the maximum number of repeats and return the number associated with that.
num, max_repeats = max(repeat_groups, key=lambda item: item[1])
if max_repeats >= 4:
return True, num
return False, None
If you run this function with print
statements added you can get output like the following:
Matrix:
[[1 0 2 2 1 0 1]
[0 2 0 2 1 1 1]
[2 2 0 0 0 0 1]
[0 0 2 2 0 2 2]
[2 1 1 1 1 1 0]
[2 2 0 2 1 0 2]]
offset -2
diag [2 0 1 2]
repeat_groups [(2, 1), (0, 1), (1, 1), (2, 1)]
num, max_repeats 2 1
offset -1
diag [0 2 2 1 1]
repeat_groups [(0, 1), (2, 2), (1, 2)]
num, max_repeats 2 2
offset 0
diag [1 2 0 2 1 0]
repeat_groups [(1, 1), (2, 1), (0, 1), (2, 1), (1, 1), (0, 1)]
num, max_repeats 1 1
offset 1
diag [0 0 0 0 1 2]
repeat_groups [(0, 4), (1, 1), (2, 1)]
num, max_repeats 0 4
(True, 0)
There is at least one left to right diagonal: 0's' # Correct!
If you want to ignore diagonals of zeros, you can easily add an extra condition, e.g.
if max_repeats >= 4 and num != 0:
return True, num
You could try and recreate this without using numpy
if you wanted.
Pythonic way to check diagonals in nested list
You can start by thinking of a means of naming the diagonals. For example, in a 3x3 matrix the indices (x, y) go like this:
(0,0) (1,0) (2,0)
(0,1) (1,1) (2,1)
(0,2) (1,2) (2,2)
If we follow the diagonals, their indices have a simple pattern. The diagonals that run lower left to upper right are:
Diag #1 (0,0)
Diag #2 (0,1) (1,0)
Diag #3 (0,2) (1,1) (2,0)
Diag #4 (1,2) (2,1)
Diag #5 (2,2)
From upper left to lower right they are:
#1 (0,2)
#2 (0,1) (1,2)
#3 (0,0) (1,1) (2,2)
#4 (1,0) (2,1)
#5 (2,0)
Notice that in the lower left to upper right case, every cell (x, y) on the same diagonal has the same value of x + y. In the other case, every cell on the same diagonal has the same value of x - y. Obviously this will be true for any size matrix. We can use x+y and x-y to name the individual diagonals. As we step through the matrix, each time we encounter a "1" we can immediately calculate the names of the two diagonals that intersect there.
This suggests an efficient algorithm for deciding if any two "1"s are on the same diagonal. In Python we can use two sets to keep track of the "occupied" diagonals. If we encounter a "1" on an already-occupied diagonal, we return True, otherwise false. We can step through the matrix in any order provided we visit all the elements.
def has_diagonal_ones(a):
# a is a 2-dimensional square array of 0's and 1's
lower_left_upper_right = set()
upper_left_lower_right = set()
for x in range(len(a)):
for y in range(len(a)):
if a[x][y] == 0:
continue
i_lower_to_upper = x + y
i_upper_to_lower = x - y
if i_lower_to_upper in lower_left_upper_right:
return True
if i_upper_to_lower in upper_left_lower_right:
return True
lower_left_upper_right.add(i_lower_to_upper)
upper_left_lower_right.add(i_upper_to_lower)
return False
L = [[1, 0, 1],
[0, 0, 0],
[0, 0, 1]]
print(has_diagonal_ones(L))
>>> True
How to get the diagonals of all rows in a 2D numpy array?
Well it seems like a specialized case of keeping diagonal elements. Here's one vectorized solution using masking
-
def keep_diag(a):
m,n = a.shape
i,j = np.ogrid[:m,:n]
mask = (i>=j) & ((i-m+n)<=j)
return a.T[mask.T].reshape(n,-1).T
Most of the trick is at the step of mask
creation, which when masked with the input array gets us the required elements off it.
Sample runs -
In [105]: a
Out[105]:
array([[ 0, 16],
[11, 98],
[81, 63],
[83, 20]])
In [106]: keep_diag(a)
Out[106]:
array([[ 0, 98],
[11, 63],
[81, 20]])
In [102]: a
Out[102]:
array([[10, 2, 66],
[44, 18, 35],
[70, 8, 31],
[12, 27, 86]])
In [103]: keep_diag(a)
Out[103]:
array([[10, 18, 31],
[44, 8, 86]])
Related Topics
Python Json Serialize a Decimal Object
How to Find the Closest Values in a Pandas Series to an Input Number
Pandas: Group by Name and Take Row With Most Recent Date
Plot Line Graph from Pandas Dataframe (With Multiple Lines)
Set Working Directory in Python/Spyder So That It's Reproducible
How to Merge Json Objects Containing Arrays Using Python
How to Strip Comma in Python String
How to Select Only One Column Using Sqlalchemy
Tensorflow: Convert Tensor to Numpy Array Without .Eval() or Sess.Run()
How to Shut Down a Python Simplehttpserver
How to Install a Conda Environment When Offline
How to Store the Result of an Executed Function and Re-Use Later
Counting CSV Column Occurrences on the Fly in Python
Python Dataframe Query With Spaces in Column Name