P2.6.4Manipulating a two-dimensional array
Write a program to read in a two-dimensional array of strings into a list of lists from a file in which the string elements are separated by one or more spaces. The number of rows, \(m\), and columns, \(n\), may not be known in advance of opening the file. For example, the text file:
A B C D
E F G H
I J K L
should create an object, grid, as:
[['A', 'B', 'C', 'D'], ['E', 'F', 'G', 'H'], ['I', 'J', 'K', 'L']]
Read like this, grid contains a list of the array's rows. Once the array has been read in, write loops to output the columns of the array:
[['A', 'E', 'I'], ['B', 'F', 'J'], ['C', 'G', 'K'], ['D', 'H', 'L']]
Harder: also output all its diagonals read in one direction:
[['A'], ['B', 'E'], ['C', 'F', 'I'], ['D', 'G', 'J'], ['H', 'K'], ['L']]
and the other direction:
[['D'], ['C', 'H'], ['B', 'G', 'L'], ['A', 'F', 'K'], ['E', 'J'], ['I']]
Show solution
The grid of strings can be read in simply by splitting each of its lines on the whitespace delimiter and appending the resulting list to the grid list. Iteration over its columns is easy enough; to get the diagonals imagine the grid shifted so that each row is displaced by one entry to the left relative to the preceding row. For the example grid in the question, this process would give:
A B C D * *
* E F G H *
* * I J K L
where * represents a "non-entry". Now the required NE-SW diagonals can be seen as the columns of this modified grid. Consider indexing this modified grid by column, \(p\) and row, \(q\). The index \(p\) increases over the columns from 0 to \(m+n-2\) inclusive. For each \(p\), the corresponding row index, \(q\), takes values in the range \(q_1 \le q < q_2\) where \(q_1=0\) for \(p < n\) (i.e. A, B, C, D) or \(q_1 = p-n+1\) for \(p \ge n\) (here, H and then L) and \(q_2 = p+1\) for \(p < m-1\) (A, E and then I) or \(q_2 = m\) for \(p \ge m\) (columns to I, J, K, L).
We don't need to store the modified array: the indices \(p, q\) are related to the original array's row and column indices, \(y\) and \(x\) through \(y = q, x = p-q\). To obtain the other diagonal entries (those in the direction NW-SE), simply imagine writing each row backwards.
grid = []
f = open('grid.txt', 'r')
for line in f.readlines():
grid.append(line.split())
f.close()
# number of rows, number of columns: ie grid is m x n
m, n = len(grid), len(grid[0])
# The grid is read in as a list of rows:
print('rows:', grid, sep='\n')
# Loop over the grid to retrieve its columns
cols = []
for x in range(n):
cols.append([])
for y in range(m):
cols[-1].append(grid[y][x])
print('cols:', cols, sep='\n')
# Retreive the NE-SW (diag1) and NW-SE (diag2) diagonals
diag1 = []
diag2 = []
for p in range(m+n-1):
diag1.append([])
diag2.append([])
q1 = 0
if p >= n:
q1 = p - n + 1
q2 = m
if p < m-1:
q2 = p+1
for q in range(q1, q2):
x, y = p - q, q
diag1[-1].append(grid[y][x])
# To get the other diagonal, read each row "backwards"
x = n - x - 1
diag2[-1].append(grid[y][x])
print('diag1:', diag1, sep='\n')
print('diag2:', diag2, sep='\n')
The output:
rows:
[['A', 'B', 'C', 'D'], ['E', 'F', 'G', 'H'], ['I', 'J', 'K', 'L']]
cols:
[['A', 'E', 'I'], ['B', 'F', 'J'], ['C', 'G', 'K'], ['D', 'H', 'L']]
diag1:
[['A'], ['B', 'E'], ['C', 'F', 'I'], ['D', 'G', 'J'], ['H', 'K'], ['L']]
diag2:
[['D'], ['C', 'H'], ['B', 'G', 'L'], ['A', 'F', 'K'], ['E', 'J'], ['I']]