Умножение двух матриц

В Python матрицу удобно представлять как вложенный список — список списков, где каждый внутренний список является строкой матрицы. В этой статье разберём два способа умножать матрицы: классический с вложенными циклами и компактный — через списковое включение.

Что нужно знать

Перед изучением примера полезно понимать:

Например, X = [[1, 2], [4, 5], [3, 6]] описывает матрицу 3×2. Первую строку можно получить как X[0], а элемент первой строки и первого столбца — как X[0][0].

Умножение матриц X и Y определено только когда число столбцов в X равно числу строк в Y. Если X имеет размер n × m, а Y — m × l, то XY имеет размер n × l (а вот YX в этом случае не определено).

Способ 1. Вложенные циклы

# Program to multiply two matrices using nested loops

# 3x3 matrix
X = [[12,7,3],\
    [4 ,5,6],\
    [7 ,8,9]]
# 3x4 matrix
Y = [[5,8,1,2],\
    [6,7,3,0],\
    [4,5,9,1]]
# result is 3x4
result = [[0,0,0,0],\
         [0,0,0,0],\
         [0,0,0,0]]

# iterate through rows of X
for i in range(len(X)):
   # iterate through columns of Y
   for j in range(len(Y[0])):
       # iterate through rows of Y
       for k in range(len(Y)):
           result[i][j] += X[i][k] * Y[k][j]

for r in result:
   print(r)

Вывод

[114, 160, 60, 27]
[74, 97, 73, 14]
[119, 157, 112, 23]

В этой программе три вложенных цикла for перебирают строки и столбцы матриц и накапливают сумму произведений в result. Способ простой, но вычислительно дорогой, особенно с ростом размерности матриц.

Совет

Для серьёзных операций над большими матрицами лучше использовать оптимизированную библиотеку NumPy — она работает в сотни и тысячи раз быстрее, чем код выше.

Способ 2. Вложенное списковое включение

# Program to multiply two matrices using list comprehension

# 3x3 matrix
X = [[12,7,3],\
    [4 ,5,6],\
    [7 ,8,9]]

# 3x4 matrix
Y = [[5,8,1,2],\
    [6,7,3,0],\
    [4,5,9,1]]

# result is 3x4
result = [[sum(a*b for a,b in zip(X_row,Y_col)) for Y_col in zip(*Y)] for X_row in X]

for r in result:
   print(r)

Результат тот же, что и в первом примере. Чтобы понять этот код, полезно знать о встроенной функции zip() и о распаковке аргументов оператором *.

Здесь вложенное списковое включение проходит по элементам матриц и сразу собирает результат. Сначала может показаться, что код сложный и плохо читается, но как только вы привыкнете к списковым включениям, переключаться обратно на вложенные циклы вряд ли захочется.