# 2017q1 Homework5 (matrix)
constributed by <`henry0929016816`>
>請注意程式碼排版
>[name=課程助教][color=red]
>感謝助教的提醒,貼 code 的時候亂掉了,沒有注意到
>[name=henry0929016816][color=blue]
## 原本 naive 版 matrix 的缺點
* 只能實作 4x4 的矩陣
不能實作其他大小的矩陣,由於 `assign` 、 `equal` 、 `mul` 函式都已經寫好 `row` 跟 `col` 的大小了,就是 ==4== 所以如果使用者傳入不是 4x4 的矩陣出來的結果可能不如預期。
* `matrix_naive.c` ==assign==
```c=
static void assign(Matrix *thiz, Mat4x4 data)
{
/* FIXME: don't hardcode row & col */
thiz->row = thiz->col = 4;
thiz->priv = malloc(4 * 4 * sizeof(float));
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
PRIV(thiz)->values[i][j] = data.values[i][j];
}
```
* `matrix_naive.c` ==equal==
```c=
static bool equal(const Matrix *l, const Matrix *r)
{
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
if (PRIV(l)->values[i][j] + epsilon < PRIV(r)->values[i][j] ||
PRIV(r)->values[i][j] + epsilon < PRIV(l)->values[i][j])
return false;
return true;
}
```
* `matrix_naive.c` ==mul==
```c=
bool mul(Matrix *dst, const Matrix *l, const Matrix *r)
{
/* FIXME: error hanlding */
dst->priv = malloc(4 * 4 * sizeof(float));
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
for (int k = 0; k < 4; k++)
PRIV(dst)->values[i][j] += PRIV(l)->values[i][k] *
PRIV(r)->values[k][j];
return true;
}
```
* 物件的實作還不夠直覺
原本的程式,如果要對 Matrix 型態的物件進行操作,必須創造 MatrixAlgo 的物件提供相關函式,才能進行對應的操作,這樣很不直覺,如果每個 Matrix 物件本身就擁有相對應的操作函式,我們就不需要多餘的建立 MatrixAlgo。
* `test-matrix.c`
```c=
int main()
{
MatrixAlgo *algo = matrix_providers[0];
Matrix dst, m, n, fixed;
algo->assign(&m, (Mat4x4) {
.values = {
{ 1, 2, 3, 4, },
{ 5, 6, 7, 8, },
{ 1, 2, 3, 4, },
{ 5, 6, 7, 8, },
},
});
}
```
## 將原版函式擴充成能實作 nxm 的矩陣
* 新增 data 到 Matrix 的資料結構
`matrix_expansion.h`
```c=
typedef struct _Matrix {
...
...
float **data;
} Matrix;
```
* 新增 create 函式,能建立任意 col 跟 row 的矩陣(未完成版)
`matrix_expansion.h`
```c=
typedef struct {
...
...
Matrix *(*create)(int rows, int cols);
} MatrixAlgo;
```
`matrix_expansion.c`
```c=
Matrix *create(int rows, int cols)
{
Matrix *m = malloc(sizeof(Matrix));
m->row = rows;
m->col = cols;
m->data = malloc(sizeof(float) * rows);
for(int i = 0; i < rows; i++)
m->data[i] = malloc(sizeof(float) * cols);
return m;
}
```
* 修改 assign 、 equal 、 mul
* `matrix_expansion.c` ==assign==
```c=
static void assign(Matrix *thiz, const Matrix *that)
{
/*map the same matrix size from that to thiz*/
thiz->row = that->row;
thiz->col = that->col;
thiz->data = malloc(sizeof(float) * thiz->row);
for(int i = 0; i < thiz->row; i++)
thiz->data[i] = malloc(sizeof(float) * thiz->col);
/*map the same data from that to thiz*/
for (int i = 0; i < thiz->row; i++)
for (int j = 0; j < thiz->col; j++)
thiz->data[i][j] = that->data[i][j];
}
```
將 thiz 矩陣的 data 使用 malloc 配置跟 that 一樣的 row 跟 col 大小,有了一樣的空間之後再賦與相同的值
* `matrix_expansion.c ==equal==
```c=
static bool equal(const Matrix *l, const Matrix *r)
{
/*if nxm matrix and axb matrix ,n!=a or m!=b you can say they
are not equal*/
if((l->row!=r->row)||(l->col!=r->col))
return false;
for (int i = 0; i < l->row; i++)
for (int j = 0; j < l->col; j++)
if (l->data[i][j] + epsilon < r->data[i][j] || r->data[i][j] + epsilon < l->data[i][j])
return false;
return true;
}
```
由於不再是只傳入 4x4 的矩陣,所以要先確認兩者是否有相同的行列數,才能開始比較兩者的值是否相同,行列數不相同就不用再比了,應為他們已經注定不是相同的陣列了。
* `matrix_expansion.c` ==mul==
```c=
bool mul(Matrix *dst, const Matrix *l, const Matrix *r)
{
/*A(nxm) matrix multiple with B(ixj) matrix,m must be equal
to i ,if m is not equal to i ,they can't mutiple.Otherwise,
the new matrix(nxj)*/
if(l->col != r->row)
return false;
dst->data = malloc(l->row * sizeof(float));
for(int i =0; i< l->row; i++)
dst->data[i]=malloc(r->col * sizeof(float));
for (int i = 0; i < l->row; i++)
for (int j = 0; j < r->col; j++)
for (int k = 0; k < l->col; k++)
dst->data[i][j] += l->data[i][k] *
r->data[k][j];
return true;
}
```
先講解矩陣乘法:
![](https://i.imgur.com/KrckiuC.png)
上面的圖表示出要如何計算 AB 的(1,2)和(3,3)元素,當 A 是個 4×2 矩陣和 B 是個 2×3 矩陣時。分別來自兩個矩陣的元素都依箭頭方向而兩兩配對,把每一對中的兩個元素相乘,再把這些乘積加總起來,最後得到的值即為箭頭相交位置的值。
* $(AB)_{1,2}=\sum_{r=1}^2{a_{1,r}\times b_{r,2}}=a_{1,1}\times b_{1,2}+a_{1,2}\times b_{2,2}$
* $(AB)_{3,3}=\sum_{r=1}^2{a_{3,r}\times b_{r,3}}=a_{3,1}\times b_{1,3}+a_{3,2}\times b_{2,3}$
如果 A 的行數 跟 B 的 列數,不相等那就無法做矩陣乘法,而如果能做矩陣乘法,則 AxB=C,C 矩陣的行列數為 A 的 列數 x B 的 行數
* 正式實作
```c=
MatrixAlgo *matrix_providers[] = {
&NaiveMatrixProvider,
};
int main()
{
MatrixAlgo *algo = matrix_providers[0];
Matrix dst, m, *n, fixed;
n=algo->create(3,4);
algo->assign(&m, n {
.data = {
{ 1, 2, 3, 4, },
{ 5, 6, 7, 4, },
{ 1, 2, 3, 4, },
},
});
}
```
執行過後,會出現一大堆==錯誤==,我才想到我沒有弄清楚 designated initializers 的技巧只能在初始化使用,如果不是初始階段,就無法使用這樣的方式給值,悲劇,只好重新修改 create 跟 assign。
* 修改 create
```c=
Matrix * create(int rows, int cols,float value[][cols])
{
Matrix *m = malloc(sizeof(Matrix));
m->row = rows;
m->col = cols;
m->data = malloc(sizeof(float) * rows);
for(int i = 0; i < rows; i++)
m->data[i] = malloc(sizeof(float) * cols);
for(int i=0; i<rows; i++)
for(int j=0; j<cols; j++)
m->data[i][j]=value[i][j];
return m;
}
```
將 create 多增加一個傳入的參數 value ,而 value 為矩陣要傳入的值,例如要
創照一個二維矩陣為 A(2x3)
$\begin{bmatrix}
A_{1,1} & A_{1,2} & A_{1,3} \\
A_{2,1} & A_{2,2} & A_{2,3}\end{bmatrix}=\begin{bmatrix}
1 & 2 & 3 \\
4 & 5 & 6\end{bmatrix}$
我們可以使用 designated initializers 的方式來建立一個二維矩陣,並將矩陣值傳給要建立的 matrix
* 實作碼
```c=
#include "..."
MatrixAlgo *matrix_providers[] = {
&NaiveMatrixProvider,
};
int main()
{
MatrixAlgo *algo = matrix_providers[0];
float value[2][3] = {
{ 1, 2, 3},
{ 4, 5, 6},
};
Matrix *A;
A=algo->create(2,3,value);
}
```
## 將 matrix 改成本身就有應對實作的函式
## 參考資料
* [你所不知道的C語言:技巧篇](https://hackmd.io/s/HyIdoLnjl#)
* [矩陣乘法](https://zh.wikipedia.org/wiki/%E7%9F%A9%E9%99%A3%E4%B9%98%E6%B3%95)