Alright, so here’s an example of matrix multiplication:

Some helpful shortcuts to remember when dealing with matrix multiplication. Let’s say we’re multiplying A*B, where A is a (4x2) matrix and B is a (2x3) matrix (like in the example above).

The number of columns of A need to match the number of rows in B. (We’re good in this case—there are 2 columns in A, and 2 rows in B.) If this isn’t true, you can’t multiply the matrices together.

The resulting matrix will have as many rows as A and as many columns as B. So in this case, the result will be a (4x3) matrix.

(By the way—if you haven’t watched the 3Blue1Brown series on “The Essence of Linear Algebra”, it’s probably the best series on linear algebra I’ve ever seen. Each video, you’ll find yourself going “I had no idea that’s what all of this was about!” It gives you an amazing intuition for what’s going on in linear algebra.)

Moving on—here’s an example of list comprehensions in Python:

And we want to create something like this…

So how do we go about solving this?

Problem Solving Part 1: Sketch Out a Solution

First, let’s think through how we’d solve this if there were no constraints.

Kind of cheating—using np.matmul()

If we weren’t restricted to just using built-in Python functions, and we didn’t need to use a list comprehension, the problem would be trivial—we could just use the NumPy matrix multiplication function.

import numpy as np def numpy_matrix_multiplication(A, B):

return np.matmul(A, B) # same as "return A @ B"

But that’s obviously defeating the purpose of this puzzle.

Using a for loop and NumPy functions

So let’s say we can’t use np.matmul(), but anything else is fine. How would we approach it then?

Well, matrix multiplication can be thought of as taking a row of A, a column of B, doing the dot product between them, and then storing that result in the new matrix. Specifically, if we take the 2nd row of matrix A and the 3rd column of matrix B, and then we take the dot product of those two, then we’ll store the result in position (2, 3) in the new matrix (the value at row 2 column 3).

Let’s try to put this idea into code using for loops. We’ll still use NumPy for the matrix dot product for now, just so we don’t have to worry about it at first.

This works! But we’re leaning pretty heavily on NumPy functions and objects currently (like NumPy arrays and the .dot() method), so in just a minute we’re going to see if we can write a for loop without some of this NumPy functionality.

(Also, notice how we’re using the built-in enumerate function to get the indices for where the value needs to go in our new matrix.)

Understanding the problem is sometimes easy, sometimes really difficult. It can often help to start with simpler versions of the problem to start with, or to break the problem down into smaller and simpler pieces. Here are some examples of that process:

Before trying to implement matrix multiplication, make sure you really understand how it’s computed. Try it for two 2x2 matrices, working out the math by hand. Google around for good explanations of what matrix multiplication is doing.

Write the simplest list comprehension you can, and then increase the complexity slowly: you could add a for loop inside the list comp; then you could add a conditional; then maybe you could try two for loops; then two loops and a conditional; etc. Build things up in complexity, trying to develop an intuitive sense of what’s going on.

Before using list comprehensions, use for loops.

Before typing anything into a computer, use pencil and paper.

Sketch things and try things even if you don’t feel like you understand what’s going on. Experimentation is a wonderful way to learn!

Problem Solving Part 2: Improve the Solution Iteratively

Now that we basically know what we’re doing, we’re going to slowly improve our solution by getting it closer to the final product. In our case, this mostly means converting everything to use built-in Python functions and objects (rather than NumPy functions and objects). We’ll also do some code cleanup at the end.

This is where the bulk of the work actually happens

Writing our own dot product (replacing np.dot)

First, let’s get rid of the NumPy dot product function. To compute the dot product of two vectors of equal length, you essentially multiply the numbers that are at the same indices of each vector and then add them all together. So let’s say you have two vectors: (a1 a2) and (b1 b2). Then the dot product would be:

dot_product = a1*b1 + a2*b2

Let’s put this into our code.

Creating the new matrix without relying on np arrays

Next, let’s think about how we can create the result matrix without using NumPy arrays to store the values as certain indices.

The way our for loops are nested, we’re going to dot product a single row of A by all of our columns in B before moving onto the next row in A. This means that these dot product values will all exist in the first row of our resulting matrix.

(Remember that if we take the dot product of the 1st row in matrix A with the nth column in matrix B, we’ll store that value in position (1, n) in our new matrix.)

This means that each time we take a row in A and iterate through dot products of the columns in B, we can create a new list with all of those results. That list will become a new row in our resulting matrix! After going through each row in A, we’ll create a list of lists—which will be exactly the matrix we’re looking for.

If you’re not sure how this works, step through the for loop yourself and see what’s happening.

Here’s the code:

A brief interlude: what happened to the list comp?

Notice that we’ve effectively forgotten about the list comprehension part of the puzzle for now.

This is totally fine! In fact, it’s necessary—before solving the problem with certain parameters, we need to figure out how to solve the problem at all.

We’re currently figuring out how to do matrix multiplication using just built-in Python functions, and this isn’t a trivial task. After figuring this out using any approach we need (in this case, a for loop), we can then move on to crafting a solution that satisfies all of the requirements—namely, using a list comprehension.

Break down a problem into simpler parts and give yourself any resources you need before trying to solve the full thing perfectly.

Transposing B without using NumPy array functionality

Currently, we’re converted each matrix into a NumPy array in the beginning of the function so that we can transpose matrix B using “B.T”. We’re transposing it like this so that we can iterate through the columns of B using the normal Python “for item in my_list” syntax.

How can we iterate through the columns of B using built-in Python functionality?