さて、思い立って行列積のプログラムを作ったわけなんですが。
「どうせ2*2とか、3*3しか出来ないものを作ったってつまらない!」という思いのもと、柔軟に対応できるプログラムを作ってみました。小数の計算も対応しています。
特徴
まずこのプログラム、行列がいくつあろうとも積が出せます。4つの行列の積とかも対応しています。
そして次に、正方行列でなくてもかまいません。(2×3)行列と(3×5)行列の積とかもちゃんとやってくれます。
初学者なりにいろいろ考えてやってみました。ソースを貼っておきます。
#include<stdio.h>
#define MAX 100
int main(void) {
printf("◆◆◆行列積計算プログラム(2015.1.5)◆◆◆\n");
printf("・指定した行列の数だけ行列積を計算することが可能です。\n");
printf("・要素の入力は整数・小数共に対応しております。\n");
printf("・現在入力可能な行列は、%d行 * %d列のまでの行列に設定されています。\n\n",MAX,MAX);
// 行列数の入力
int n;
do {
printf("◆計算する行列の数を入力してください\n");
scanf("%d",&n);
if(n<2) {
printf("警告: 2以上の整数を入力してください\n\n");
}
} while(n<2);
//int n = 2; //行列数。今は便宜上2。あとで消します
int y_1,x_1; //1つ目の行列は何行何列か?
int x_2,z_2; //2つ目の行列は何行何列か?
int i;
int count_row;
int j,k;
for(i=1;i<=n-1;i++) {
if(i==1) { //最初だけ行列のサイズを2回聞く
do {
printf("◆%dつ目の行列は何行何列ですか?(例:3行5列の場合 3 5)\n",i);
scanf("%d",&y_1); scanf("%d",&x_1);
if(x_1>MAX ||y_1>MAX) {
printf("警告: 数字が大きすぎます。%d以下の整数を入力してください。\n\n",MAX);
}
else if(x_1<=0||y_1<=0) {
printf("警告: 正の整数を入力してください。\n\n");
}
}while((x_1>MAX ||y_1>MAX)||(x_1<=0||y_1<=0));
}
double a[x_1][y_1];
if(i==1) {
printf("◆%dつめの行列(%d行%d列)の要素を入力してください。\n",i,y_1,x_1);
for(j=0;j<y_1;j++) { //行ループ
for(k=0;k<x_1;k++) { //列ループ
scanf("%lf",&a[k][j]);
}
}
//1個目の行列の書き出し
printf("入力された行列は、\n");
for(count_row=0;count_row<y_1;count_row++) { //行ループ
//ここから左の罫線
if(count_row==0 && y_1 != 1) {
printf("┏ ");
}
else if(count_row== 0 && y_1 == 1) {
printf("[ ");
}
else if(count_row == y_1 - 1) {
printf("┗ ");
}
else {
printf("┃ ");
} //ここまで左の罫線
for(j=0;j<x_1;j++) { //列ループ
printf("%5g ",a[j][count_row]);
}
//ここから右の罫線
if(count_row==0 && y_1 != 1) {
printf("┓");
}
else if(count_row== 0 && y_1 == 1) {
printf("]");
}
else if(count_row == y_1 - 1) {
printf("┛");
}
else {
printf("┃");
} //ここまで右の罫線
printf("\n"); //1列書き終えたら改行
}
printf("\n");
//1個目の行列の書き出しここまで
x_2 = x_1;
}
do {
printf("◆%dつ目の行列は%d行何列ですか?\n",i+1,x_2);
scanf("%d",&z_2);
if(z_2>MAX) {
printf("警告: 数字が大きすぎます。%d以下の整数を入力してください。\n\n",MAX);
}
else if(z_2<=0) {
printf("警告: 正の整数を入力してください。\n\n");
}
}while(z_2>MAX||z_2<=0);
double ret[z_2][y_1];
int result_row;
printf("◆%dつめの行列(%d行%d列)の要素を入力してください。\n",i+1,x_2,z_2);
double b[z_2][x_2];
for(j=0;j<x_2;j++) { //行ループ
for(k=0;k<z_2;k++) { //列ループ
scanf("%lf",&b[k][j]);
}
}
//2個目以降の行列の書き出し
printf("入力された行列は、\n");
for(count_row=0;count_row<x_2;count_row++) { //行ループ
//ここから左の罫線
if(count_row==0 && x_2 != 1) {
printf("┏ ");
}
else if(count_row== 0 && x_2 == 1) {
printf("[ ");
}
else if(count_row == x_2 - 1) {
printf("┗ ");
}
else {
printf("┃ ");
} //ここまで左の罫線
for(j=0;j<z_2;j++) { //列ループ
printf("%5g ",b[j][count_row]);
}
//ここから右の罫線
if(count_row==0 && x_2 != 1) {
printf("┓");
}
else if(count_row== 0 && x_2 == 1) {
printf("]");
}
else if(count_row == x_2 - 1) {
printf("┛");
}
else {
printf("┃");
} //ここまで右の罫線
printf("\n"); //1列書き終えたら改行
}
printf("\n");
//2個目以降の行列の書き出しここまで
double c[MAX][MAX]; //結果一時記憶用の配列。
if(i==1) { //初回はaとbをかける
for(result_row=0;result_row<y_1;result_row++) { //行ループ
for(j=0;j<z_2;j++) { //列ループ
double el_mtrx = 0;
int cnt;
for(cnt=0;cnt<x_2;cnt++) {
el_mtrx += a[cnt][result_row] * b[j][cnt];
}
ret[j][result_row] = el_mtrx;
}
}
//配列をretからcにコピー
for(result_row=0;result_row<y_1;result_row++) { //行ループ
for(j=0;j<z_2;j++) { //列ループ
c[j][result_row] = ret[j][result_row];
}
}
}
else { //2回目以降はcとbをかける
for(result_row=0;result_row<y_1;result_row++) { //行ループ
for(j=0;j<z_2;j++) { //列ループ
double el_mtrx = 0;
int cnt;
for(cnt=0;cnt<x_2;cnt++) {
el_mtrx += c[cnt][result_row] * b[j][cnt];
}
ret[j][result_row] = el_mtrx;
}
}
//配列をretからcにコピー
for(result_row=0;result_row<y_1;result_row++) { //行ループ
for(j=0;j<z_2;j++) { //列ループ
c[j][result_row] = ret[j][result_row];
}
}
}
if(i==n-1) { //以下、結果の書き出し操作。最後だけ行うようにする
printf("行列積:\n");
for(result_row=0;result_row<y_1;result_row++) { //行ループ
//ここから左の罫線
if(result_row==0 && y_1 != 1) {
printf("┏ ");
}
else if(result_row== 0 && y_1 == 1) {
printf("[ ");
}
else if(result_row == y_1 - 1) {
printf("┗ ");
}
else {
printf("┃ ");
} //ここまで左の罫線
for(j=0;j<z_2;j++) { //列ループ
printf("%6g ",ret[j][result_row]);
}
//ここから右の罫線
if(result_row==0 && y_1 != 1) {
printf("┓");
}
else if(result_row== 0 && y_1 == 1) {
printf("]");
}
else if(result_row == y_1 - 1) {
printf("┛");
}
else {
printf("┃");
} //ここまで右の罫線
printf("\n"); //1列書き終えたら改行
}
//結果の書き出しここまで
}
x_2 = z_2; //行列積定義可能になるように正しく実行させるため
}
return 0;
}
解説
解説というほどのことでもありませんが・・・。
このプログラムは2次元配列がカギです。どれがどの配列だか、どれがどの変数だかまぎらわしいことこの上ないですが、そこは頑張りでなんとか。最初に入力された2つの行列積を計算して、その結果と次の行列の積を計算して・・・という流れになっています。
3つ以上の行列積を計算するにあたって、一時的にそれまでの結果を保存するための配列が必要なのですが、最初は結果の行列のサイズに合わせて配列のサイズも変える予定でした。でも、無理でしたので"MAX"で配列のサイズの上限を設定できるようにして、サイズを100*100とかにしてごまかしました。アイデアある方コメントください・・・。
ループ上で配列のサイズを変えるのは私の技量では無理でした。いつか改良して、少しでも少ないメモリ確保で動かせるようにしたいものですね。
欠点
実際に使う分には問題ないのですが、自分で定義する関数を一切使っていないのでソースが非常に見づらいのが欠点ですね。最初に2次元配列2個と、配列のサイズを渡して関数をつくろう、とは考えていたのですが、2次元配列を関数に渡すときには少なくとも後方の要素数がわかっていないとエラーになっちゃうのです。
どういうことか?というと、配列というのはコンピュータ側から見ると単に数字データの羅列であって、私たちが見ているようにきれいな長方形型ではない、つまり行や列の区切りが不明なのです。なので2次元配列の変数がどちらも不明だと、どこでn行目が終わって、どこから(n+1)行目なのかがわからず、正確に配列が把握できずにコンパイルエラーになります。少なくとも何列あるのかがわかれば、その問題は解決できますね。
よって、2次元配列a[10][10] があるとしたら、関数に渡すときはa[][10]という書き方で渡すことは出来ます。しかし、a[][]では渡せない、ということになるのです。
以上の問題から、関数を作るのは無理なんじゃないかと思ってやめました。実はできるのかな・・・
コメント大歓迎です。それでは。