Exam Code Python Programming fundamentals Exam level Tested code
Solved Python exam exercise: a bike-share trip matrix with transpose, diagonal sums, row and column totals and the net balance of each station.

A bike-share scheme has n docking stations numbered 0 to n-1. At the end of each day the system produces a square matrix m of non-negative integers: m[i][j] is the number of trips that started at station i and ended at station j. The main diagonal, m[i][i], records trips that returned to the station they left from (internal trips).
Input (from standard input): a first line with the integer n (0 ≤ n ≤ 50), followed by n lines of n space-separated integers (each value between 0 and 1 000 000).
Output: if n is 0, the program prints a single line EMPTY MATRIX. Otherwise it prints, in this order: (1) a line TRANSPOSE followed by the n rows of the transposed matrix, values separated by one space; (2) OUTGOING: followed by the space-separated number of trips leaving each station for another station (the row sum without the diagonal element); (3) INCOMING: followed by the trips arriving at each station from another one (the column sum without the diagonal); (4) INTERNAL: and the sum of the main diagonal; (5) ANTI_DIAGONAL: and the sum of the anti-diagonal (elements m[i][n-1-i]); (6) ASYMMETRIC: and the number of station pairs i < j with m[i][j] != m[j][i]; (7) MAX_BALANCE: and the index of the station with the largest balance incoming - outgoing. On a tie, the smallest index wins.
Marks are awarded for decomposing the program into functions with a single clear responsibility, for not modifying the matrix that was read, and for producing exactly the output format above. External libraries are not allowed.
Sample input
3
0 5 2
3 1 4
2 0 6Expected output
TRANSPOSE
0 3 2
5 1 0
2 4 6
OUTGOING: 7 7 2
INCOMING: 5 5 6
INTERNAL: 7
ANTI_DIAGONAL: 5
ASYMMETRIC: 2
MAX_BALANCE: 2- Correct input reading (including the empty matrix) and exact output format
- 0.5
- Building the transpose without modifying the original matrix
- 0.75
- Outgoing and incoming totals excluding the diagonal; main and anti-diagonal sums
- 0.75
- Counting asymmetric pairs without double counting, and choosing the station with the highest balance with the correct tie-break
- 0.5
Hints
Hint 1 · How do I get column sums without writing another loop?
Notice that column j of m is exactly row j of its transpose. Since you must build the transpose anyway to print it, one function that sums rows serves for both outgoing and incoming totals. And the diagonal does not move when you transpose.
Hint 2 · Which indices does each diagonal visit?
The main diagonal is the set of cells where row and column coincide: (i, i). The anti-diagonal runs from the top-right corner to the bottom-left one: in row i its column is n-1-i. A single loop with i from 0 to n-1 is enough; you do not need a nested loop with an if inside.
Hint 3 · How do I count each pair only once?
If you visit every (i, j) with i != j, each asymmetric pair shows up twice: as (i, j) and as (j, i). Walk only the upper triangle, with j starting at i + 1. For the balance, keep the best value seen so far and replace it only on a strict comparison: the tie-break in favour of the smaller index then comes for free.
Solution
Explained solution
The key insight is that almost everything reduces to walking rows. We build the transpose once, because we have to print it, and reuse it for the incoming totals: summing the rows of the transpose is summing the columns of the original. Everything else is a walk along one diagonal, along two diagonals, or over the upper triangle.
The program is split into small functions that take the matrix and return a result without changing it. That way each rubric criterion maps onto a function you can test on its own.
import sys
def read_matrix(text):
tokens = text.split()
if not tokens:
return []
n = int(tokens[0])
values = [int(x) for x in tokens[1:1 + n * n]]
return [values[i * n:(i + 1) * n] for i in range(n)]
def transpose(m):
n = len(m)
t = [[0] * n for _ in range(n)]
for i in range(n):
for j in range(n):
t[j][i] = m[i][j]
return t
def off_diagonal_flows(m):
return [sum(m[i]) - m[i][i] for i in range(len(m))]
def diagonal_sums(m):
n = len(m)
main_sum = 0
anti_sum = 0
for i in range(n):
main_sum += m[i][i]
anti_sum += m[i][n - 1 - i]
return main_sum, anti_sum
def asymmetric_pairs(m):
n = len(m)
count = 0
for i in range(n):
for j in range(i + 1, n):
if m[i][j] != m[j][i]:
count += 1
return count
def max_balance_station(incoming, outgoing):
best = 0
best_balance = incoming[0] - outgoing[0]
for i in range(1, len(incoming)):
balance = incoming[i] - outgoing[i]
if balance > best_balance:
best = i
best_balance = balance
return best
def main():
m = read_matrix(sys.stdin.read())
if not m:
print("EMPTY MATRIX")
return
t = transpose(m)
outgoing = off_diagonal_flows(m)
incoming = off_diagonal_flows(t)
main_sum, anti_sum = diagonal_sums(m)
print("TRANSPOSE")
for row in t:
print(" ".join(str(x) for x in row))
print("OUTGOING: " + " ".join(str(x) for x in outgoing))
print("INCOMING: " + " ".join(str(x) for x in incoming))
print("INTERNAL: " + str(main_sum))
print("ANTI_DIAGONAL: " + str(anti_sum))
print("ASYMMETRIC: " + str(asymmetric_pairs(m)))
print("MAX_BALANCE: " + str(max_balance_station(incoming, outgoing)))
main()read_matrix reads the whole input at once and splits it into tokens with split(), so extra spaces or blank lines added by the marker do no harm. The first token is n; the next n*n tokens are cut into rows of length n with the slices values[i*n:(i+1)*n]. If there are no tokens, or n is 0, the resulting list is empty and main prints EMPTY MATRIX. That is the edge case covered by the test "Empty matrix".
transpose creates a fresh matrix t with [[0] * n for _ in range(n)] and copies each m[i][j] into t[j][i]. It matters that the rows are built with a list comprehension and not with [[0] * n] * n, which would repeat the same row object n times. The original matrix is left untouched, so we can keep using it for the outgoing totals and the symmetry check.
off_diagonal_flows sums each row and subtracts its diagonal element, because an internal trip neither leaves for another station nor arrives from one. Applied to m it gives the outgoing totals. Applied to t it gives the incoming ones, since row j of t is column j of m and both share the same diagonal. In the example, row 0 of m sums to 7 with diagonal 0, and column 2 sums to 12 with diagonal 6: hence the 7 in OUTGOING and the 6 in INCOMING.
diagonal_sums walks the index i once and accumulates both m[i][i] and m[i][n-1-i]. When n is odd, the centre cell belongs to both diagonals and is added to both, as the statement requires. In the example, cell (1, 1) holds 1 and contributes to INTERNAL: 7 as well as to ANTI_DIAGONAL: 5. In the single-station test both diagonals are the same cell, so both sums are 7.
asymmetric_pairs walks only the upper triangle (j from i + 1) and compares each cell with its mirror. The example has three pairs: (0,1) with 5 against 3, (0,2) with 2 against 2, and (1,2) with 4 against 0. Two of them differ, so the result is 2. In the symmetric 4×4 test, the printed transpose equals the original and the count is 0.
max_balance_station starts with station 0 as the candidate and only replaces it on a strictly larger balance. In the symmetric test every balance is 0 and station 0 wins: the tie-break comes from using > rather than >=. In the test "One-way flow", station 1 receives 10 trips and sends none, so it is the one chosen.
Step-by-step trace · generated by running the code
| Step | phase | i | j | value | asymmetric | Cell |
|---|---|---|---|---|---|---|
| 1 | transpose | 0 | 0 | 0 | 0 | (0,0) |
| 2 | transpose | 0 | 1 | 5 | 0 | (0,1) |
| 3 | transpose | 0 | 2 | 2 | 0 | (0,2) |
| 4 | transpose | 1 | 0 | 3 | 0 | (1,0) |
| 5 | transpose | 1 | 1 | 1 | 0 | (1,1) |
| 6 | transpose | 1 | 2 | 4 | 0 | (1,2) |
| 7 | transpose | 2 | 0 | 2 | 0 | (2,0) |
| 8 | transpose | 2 | 1 | 0 | 0 | (2,1) |
| 9 | transpose | 2 | 2 | 6 | 0 | (2,2) |
| 10 | symmetry | 0 | 1 | 5 | 1 | (0,1) |
| 11 | symmetry | 0 | 2 | 2 | 1 | (0,2) |
| 12 | symmetry | 1 | 2 | 4 | 2 | (1,2) |
Test cases
| Case | Input | Expected output | Actual output | Result |
|---|---|---|---|---|
| Statement example | 3
0 5 2
3 1 4
2 0 6 | TRANSPOSE
0 3 2
5 1 0
2 4 6
OUTGOING: 7 7 2
INCOMING: 5 5 6
INTERNAL: 7
ANTI_DIAGONAL: 5
ASYMMETRIC: 2
MAX_BALANCE: 2 | TRANSPOSE
0 3 2
5 1 0
2 4 6
OUTGOING: 7 7 2
INCOMING: 5 5 6
INTERNAL: 7
ANTI_DIAGONAL: 5
ASYMMETRIC: 2
MAX_BALANCE: 2 | OK |
| Single station | 1
7 | TRANSPOSE
7
OUTGOING: 0
INCOMING: 0
INTERNAL: 7
ANTI_DIAGONAL: 7
ASYMMETRIC: 0
MAX_BALANCE: 0 | TRANSPOSE
7
OUTGOING: 0
INCOMING: 0
INTERNAL: 7
ANTI_DIAGONAL: 7
ASYMMETRIC: 0
MAX_BALANCE: 0 | OK |
| Empty matrix | 0 | EMPTY MATRIX | EMPTY MATRIX | OK |
| Symmetric matrix with a tie | 4
1 2 3 4
2 5 6 7
3 6 8 9
4 7 9 0 | TRANSPOSE
1 2 3 4
2 5 6 7
3 6 8 9
4 7 9 0
OUTGOING: 9 15 18 20
INCOMING: 9 15 18 20
INTERNAL: 14
ANTI_DIAGONAL: 20
ASYMMETRIC: 0
MAX_BALANCE: 0 | TRANSPOSE
1 2 3 4
2 5 6 7
3 6 8 9
4 7 9 0
OUTGOING: 9 15 18 20
INCOMING: 9 15 18 20
INTERNAL: 14
ANTI_DIAGONAL: 20
ASYMMETRIC: 0
MAX_BALANCE: 0 | OK |
| One-way flow | 2
0 10
0 0 | TRANSPOSE
0 0
10 0
OUTGOING: 10 0
INCOMING: 0 10
INTERNAL: 0
ANTI_DIAGONAL: 10
ASYMMETRIC: 1
MAX_BALANCE: 1 | TRANSPOSE
0 0
10 0
OUTGOING: 10 0
INCOMING: 0 10
INTERNAL: 0
ANTI_DIAGONAL: 10
ASYMMETRIC: 1
MAX_BALANCE: 1 | OK |
Actual outputs: code compiled with Python 3.13.16 and run in an isolated container on 4 October 2026.
Complexity
Time: O(n²). Reading processes n² numbers, transpose visits every cell once, each call to off_diagonal_flows sums n rows of n elements, and asymmetric_pairs visits n(n-1)/2 pairs. The diagonals and the balance are linear. With n ≤ 50 this is a few thousand operations. You cannot do better than O(n²), because any correct solution has to read every cell.
Memory: O(n²) extra for the transpose, plus O(n) for the outgoing and incoming lists. If the statement did not require printing the transpose, the incoming totals could be computed by summing columns directly on m, and the extra memory would drop to O(n).
Common mistakes
- Building the transpose with
[[0] * n] * n: every row is the same object, so each assignment tot[j][i]changes all rows at once. - Transposing in place by swapping
m[i][j]andm[j][i]for everyjfrom 0 ton-1: each pair is swapped twice and the matrix ends up unchanged. You also lose the original matrix you still need. - Forgetting to subtract the diagonal from the outgoing and incoming totals, so internal trips are counted as if they went between different stations.
- Writing the anti-diagonal as
m[i][n-i]: in row 0 it raises anIndexError, and in every other row it adds the wrong cell. - Counting asymmetric pairs over the whole matrix instead of the upper triangle, which doubles the result.
- Using
>=when searching for the maximum balance, so a tie goes to the larger index instead of the smaller one.
Variants
Do it with zip and comprehensions
In Python, zip(*m) groups the elements that sit at the same position in each row, which means it yields the columns: a one-line transpose. You should still be able to write it by hand, because exam questions often ask for the explicit nested loop. This version produces the same output on every test; note that balances.index(max(balances)) returns the first occurrence, which gives the required tie-break.
import sys
def main():
data = sys.stdin.read().split()
n = int(data[0]) if data else 0
if n == 0:
print("EMPTY MATRIX")
return
v = [int(x) for x in data[1:1 + n * n]]
m = [v[i * n:(i + 1) * n] for i in range(n)]
t = [list(col) for col in zip(*m)]
outgoing = [sum(r) - r[i] for i, r in enumerate(m)]
incoming = [sum(c) - c[i] for i, c in enumerate(t)]
main_sum = sum(m[i][i] for i in range(n))
anti_sum = sum(m[i][n - 1 - i] for i in range(n))
asym = sum(1 for i in range(n) for j in range(i + 1, n) if m[i][j] != m[j][i])
balances = [a - b for a, b in zip(incoming, outgoing)]
best = balances.index(max(balances))
print("TRANSPOSE")
for row in t:
print(" ".join(map(str, row)))
print("OUTGOING: " + " ".join(map(str, outgoing)))
print("INCOMING: " + " ".join(map(str, incoming)))
print("INTERNAL: " + str(main_sum))
print("ANTI_DIAGONAL: " + str(anti_sum))
print("ASYMMETRIC: " + str(asym))
print("MAX_BALANCE: " + str(best))
main()Rotate the matrix 90 degrees clockwise
A 90° clockwise rotation is a transpose followed by reversing each row: r = [row[::-1] for row in transpose(m)]. Check the index formula: element m[i][j] ends up at r[j][n-1-i]. This is a common follow-up to see whether you understand the transpose as an index transformation rather than a memorised recipe.
Produced with AI support and reviewed by the newsroom



Comentarios
Publicar un comentario