Matrix multiplication in scheme, List of lists
Asked Answered
B

3

4

I started to study Scheme and I do not understand some of it. I'm using DrRacket.

I wrote the following code:

(define mult_mat
  (λ (A B)
    (Trans_Mat (map (λ (x) (mul_Mat_vec A x))
                    (Trans_Mat B)))))

That uses this functions:

(define Trans_Mat
  (λ (A)
    (apply map (cons list A))))

(define mul_Mat_vec
  (λ (A v)
    (map (λ (x) (apply + (map * x v)))
         A)))

In mult_mat, I multiply the matrix A in each vector of the transpose matrix B. It works fine.

I found a code on the web that makes the multiplication in a way that I don't understand:

(define (matrix-multiply matrix1 matrix2)
  (map
   (λ (row)
     (apply map
       (λ column
         (apply + (map * row column)))
       matrix2))
   matrix1))

In this code, row is a list of the lists of matrix A, but I don't understand how the column updates.

This part of the code: (apply + (map * row column)) is the dot product of vector row and vector column

For example: A is a matrix 2X3 and B is a matrix 3X2 and if instead of (apply + (map * row column)) I write 1, then I'll get a matrix 2X2 with entries valued 1

I don't understand how it works.

Thanks.

Bikaner answered 5/9, 2018 at 11:30 Comment(1)
If the code works, your question is probably off-topic here.Lockman
R
8

Ah, the old ( apply map foo _a_list_ ) trick. Very clever.

In fact (apply map (cons list A)) is the same as (apply map list A). That's just how apply is defined to work.

Trying out some concrete examples usually helps to "get it":

(apply map       list '((1 2 3)  (10 20 30)) )
=
(apply map (cons list '((1 2 3)  (10 20 30))))
=
(apply map (list list  '(1 2 3) '(10 20 30) ))
=
(      map       list  '(1 2 3) '(10 20 30)  )
=
'((1 10) (2 20) (3 30))

so that the elements of the last argument, '((1 2 3) (10 20 30)), are spliced in into the wholeapply map ... form.

Matrix transposition... (list of lists, really).

So you have

(define (mult_mat A B)
    (Trans_Mat (map (λ (B_column) (mul_Mat_vec A B_column))
                    (Trans_Mat B))))

(define (Trans_Mat A)
    (apply map list A))

(define (mul_Mat_vec A v)
    (map (λ (A_row) (apply + (map * A_row v)))
         A))

(define (matrix-multiply A B)
  (map
    (λ (A_row)
      (apply map
             (λ B_column
               (apply + (map * A_row B_column)))
             B))
    A))

Notice it's (λ B_column ..., without parentheses. In ((λ args ...) x y z), when the lambda is entered, args gets all the arguments packaged in a list:

((λ args ...) x y z)
=
(let ([args (list x y z)])
  ...)

Also notice

      (apply map
             (λ B_column
               (apply + (map * A_row B_column)))
             B)

follows the same "tricky" pattern. It's in fact the same as

      (apply map (cons
             (λ B_column
               (apply + (map * A_row B_column)))
             B    ) )
=
      (      map
             (λ B_column
                (apply + (map * A_row B_column)))
             B_row1
             B_row2
             ....
             B_rowN )
=
     (cons (let ([B_column_1 (map car B)])
              (apply + (map * A_row B_column_1)))
           (map (λ B_column
                    (apply + (map * A_row B_column)))
             (cdr B_row1)
             (cdr B_row2)
             ....
             (cdr B_rowN)) )
=
     (cons 
       (apply (λ B_column (apply + (map * A_row B_column)))
              (map car B))
       (apply map
              (λ B_column
                 (apply + (map * A_row B_column)))
              (map cdr B)))

by the definition of map.

Thus, by applying the map, the matrix is "opened up" into the list of its elements the rows, and then when the multi-argument map gets to work on these rows as its arguments, the lambda function gets applied to each row's subsequent numbers, in unison, correspondingly; thus achieving the same effect as the explicit transposition would. But now the added bonus is, we don't need to transpose the result back into the proper form, as we had to with the first version.

This is very clever, and nice.


So, armed with all this understanding, let's try re-reading the original code and see if we can see into it as it is as well.

(define (matrix-multiply matrix1 matrix2)
  (map
   (λ (row)
     (apply map
       (λ column      ;; <<------ no parens!
         (apply + (map * row column)))
       matrix2))
   matrix1))

This reads: for each row in matrix1, multi-arg map a lambda over matrix2. matrix2 is itself also a list of rows; when we multi-arg-map over the rows, the lambda gets applied to each column in the rows in turn.

So, for each row in matrix1, for each column in matrix2, multiply that row and that column element-wise and sum the results; thus transforming each row into the list of these sums. This obviously works out only if the length of the row and the lengths of each of the columns are the same: if the "width" of the first matrix and the "height" of the second matrix are the same.

Rostov answered 5/9, 2018 at 17:34 Comment(3)
Hi, are there built in libraries to do this? I've looked at (require math/array), but I don't think it supports the common operations: multiplication, inverse, etc. I could reimplement it myself, but this is only a tiny part of my app and I'd like to take "great artists steal" to heart. I've seen that CL supports this (reddit.com/r/lisp/comments/6b21ka/…), and numpy has proven quite good at this when I've used it, but I'd like to do it in Racket. Thanks!Barathea
sorry, don't know. Racket has searchable documentation though.Rostov
(require math/matrix) is the inbuilt library docs.racket-lang.org/math/matrices.html?q=math for multiplication etcDunne
A
0

If you prefer to use while loops (which may be easier for a beginner), I recommend splitting the problem into 7 main helper functions (along with some other simple functions):

This is not the most efficient method (by far), but it is easy to understand

  1. getRow mat i: Gets row i of matrix mat (list of lists)

     (define (getRow mat i)
        (nthElement mat i))
    
    (define (nthElement lisT n)
        (if (= n 0)
            (car lisT)                                                 
            (nthElement (cdr lisT) (- n 1))))
    
  2. getCol mat i: Gets column i of matrix mat (list of lists)

    (define (getCol mat i)
        (define col (list))
        (define row 0)
        (while (< row (length mat))
            (set! col (append col (list (valueAtIJ mat row i))))
            (set! row (+ row 1)))
         col)
    
    (define (valueAtIJ mat i j)
        (nthElement (nthElement mat i) j))
    
  3. listMult list1 list2: Performs element-wise multiplication on two lists

    (define (listMult list1 list2)
        (if (not (null? list1))
            (cons (* (car list1) (car list2)) (listMult (cdr list1) (cdr list2)))
            null))
    
  4. sum aList: Calculates the sum of all the elements in a list

    (define (sum aList)
        (if (null? aList)
            0
            (+ (car aList) (sum (cdr aList)))))
    
  5. length aList: Finds the length of a list

    (define (length lisT)
        (if (null? lisT)                                               
            0
            (+ 1 (length (cdr lisT)))))
    
  6. newMatrix m n val: Create an m by n matrix filled with val

    (define (newMatrix m n val)                        
        (define i 0)
        (define row (list val))
        (define mat (list))
        (if (= n 0)
            (list)                                          
            (begin 
                (while (< i (- n 1))
                    (set! row (append row (list val))) 
                    (set! i (+ i 1)))
                (set! i 0)
                (while (< i m)
                    (set! mat (append mat (list row)))     
                    (set! i (+ i 1)))
        mat)))
    
  7. setValueAtIJ mat i j val: Set the value val at position i,j in mat (0-based)

    (define (setValueAtIJ mat i j val)
        (set! mat (setNthElementFinal mat i (setNthElementFinal (nthElement mat i) j val)))
        mat) 
    
    

These can all be combined to create the matrix multiplication function

(define (matrixMult mat1 mat2)
    (define mat1Dim (list (length mat1) (length (nthElement mat1 0))))      
    (define mat2Dim (list (length mat2) (length (nthElement mat2 0))))      
    (define i 0)
    (define j 0)
    (define newMat (newMatrix (car mat1Dim) (car (cdr mat2Dim)) 0))        
    (if (not (= (car (cdr mat1Dim)) (car mat2Dim)))
        null                                                                
        (begin
            (while (< i (length newMat))
                (while (< j (length (nthElement newMat 0)))
                    (set! newMat (setValueAtIJ newMat i j (sum (listMult (getRow mat1 i) (getCol mat2 j)))))
                    (set! j (+ j 1)))
                (set! j 0)
                (set! i (+ i 1)))
        newMat)))
Adrastus answered 17/8, 2020 at 21:27 Comment(2)
Wouldn't it be better to write C code in C and Scheme code in Scheme?Guillen
I'm a beginner to Scheme as well. Just trying to provide another perspective. This was also my first post on Stack Overflow.Adrastus
L
0

This solution might not the best way to write it, but it's easy to understand:

(define (matrixMultiply matrix1 matrix2)
    (define matrix2Transpose (matrixTranspose matrix2) ) ; Calculate matrix2 transpose to prevent recalculation in future
    (map 
        (lambda (row) ; Step1. Iterate through matrix1 rows
            (map
                (lambda (column) ; Step3. Iterate through matrix2 columns
                    (apply + (map * row column)) ; Step4. Multiply rows and columns by peer to peer and add them
                    ; Example: 
                    ; If row be (1 2) and column be (5 7) then:
                    ; Map part does: ((1 * 5) (2 * 7)) -> (5 14)
                    ; Apply part does: 5 + 14 -> 19
                )
                matrix2Transpose ; Step2. Use matrix2 transpose to get columns for every iteration
            )
        )
        matrix1
    )
)

(define (matrixTranspose matrix)
    (apply map (lambda _ _) matrix)
)

(display 
    (matrixMultiply '((1 2) (3 4)) '((5 6) (7 8)) )
)

Output: ((19 22) (43 50))

Litre answered 12/5, 2021 at 0:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.