C言語で作る添え字フリーなベクトル&行列

配列の添え字はいつも0からはじまります。1から使えるようにするにはどうすればいいでしょうか。例えば使っているイメージはこんな感じ。↓

double* v = cvector(1,3);
v[1] = 1;
v[2] = 2;
v[3] = 3;
print_cvector(v,1,3);
free_cvector(v,1,3);

C言語を使って、添え字の範囲を指定できるベクトルを作成してみます。(動作確認はcygwin上。色々、間違いがありそうなので注意)

ベクトルの作成

まずはヘッダに必要な関数を考えます。

cvector.h
#ifndef __CVECTOR_H
#define __CVECTOR_H

/*最初と最後のindexを指定して任意のサイズのvectorを生成する*/
double *cvector(int min_i,int max_i);
/*最初と最後のindexを指定したvector領域を解放する*/
void free_cvector(double *v,int min_i,int max_i);

/*vectorをコンソールに表示する*/
void print_cvector(double *v,int min_i,int max_i);

#endif

cvectorはベクトル生成、free_cvectorは解放する関数です。最低限この二つを実装すれば、ベクトルの計算を行う関数を簡単に作ることができます。

cvector.c

ベクトルを生成する関数を実装します。仕様はこんな感じです。
引数型:int,引数名:min_i //最初の要素のindex :1番目の要素をv[min_i]で指定
引数型:int,引数名:max_i //最後の要素のindex
vectorのrangeは[min_i..max_i],sizeはmax_i-max_i+1
返却値型:int*,返却値: //領域が確保できた場合,先頭アドレス,確保できなかった場合NULL

double *cvector(int min_i,int max_i){
	/*vectorサイズ計算*/
	int size=max_i-min_i+1;
	if(size<=0){
		printf("invalid input indexes\n");/*入力indexが不正です*/
		return NULL;
	}
	/*size分の領域確保*/
	double* v=(double *)malloc(sizeof(double)*size);
	/*確保成功*/
	if(v!=NULL){
		/*最初のindexだけ先頭アドレスをずらす*/
		v=v-min_i;
		return v;
	}
	/*失敗*/
	else{
		printf("memory allocation error\n");/*vector用メモリを確保できませんでした*/
		return NULL;
	}
}

この関数で重要な点は、引数で指定した値だけずらして返却するというところにあります。添え字フリーな行列も全く同様に作ることができます。

v=v-min_i;
return v;
free_cvector

解放するときは生成したときとは逆に、引数分だけ先をターゲットにしなければいけません。

void free_cvector(double *v,int min_i,int max_i){
	/*入力値がNULLでない場合にのみ解放*/
	if(v!=NULL){
		/*最初のindexが0となるように先頭アドレスをずらす*/
		v=v+min_i;
		free(v);
		v=NULL;
	}
}
print_cvector

ちなみにprint_cvector。

void print_cvector(double *v,int min_i,int max_i){
	int i;

	printf("(");
	for(i=min_i;i<=max_i;i++){
		printf("%f ",v[i]);
	}
		printf(")\n");
}

実際に使う場合は直接添え字にアクセスすれば大丈夫です。例えば添え字が1〜3なら、下のように簡単に使うことができます。

	double innerProduct = 0;
	for(i = 1; i < 3; ++i){
		innerProduct += v[i] * u[i];
	}

ちなみに最初の例で使ってみると下ようになります。

cvectorTest.c
#include <stdio.h>
#include "cvector.h"

int main(int argc, char *argv[]){

	double* v = cvector(1,3);
	double* w=cvector(-3,-1);

	v[1] = 1;
	v[2] = 2;
	v[3] = 3;

	print_cvector(v,1,3);
	free_cvector(v,1,3);

	w[-3]=-3;
	w[-2]=-2;
	w[-1]=-3;

	print_cvector(w,-3,-1);
	free_cvector(w,-3,-1);
	return 0;
}

(1.000000 2.000000 3.000000 )
(-3.000000 -2.000000 -3.000000 )

行列

次に行列を作ってみます。行(row)と列(col)の添え字を自由に指定できるとしましょう

matrix.h
#ifndef __CMATRIX_H
#define __CMATRIX_H

/*最初と最後のindexを指定して任意のサイズのmatrixを生成する*/
double **cmatrix(int min_r,int max_r,int min_c,int max_c);

/*最初と最後のindexを指定したmatrix領域を解放する*/
void free_cmatrix(double **mat,int min_r,int max_r,int min_c,int max_c);

/*matrixをコンソールに表示する*/
void print_cmatrix(double **mat,int min_r,int max_r,int min_c,int max_c);

#endif
matrix.c
cmatrix

行列を生成する関数を作ります。下のような仕様で作成します。
引数型:int,引数名:min_r //最小の行index
引数型:int,引数名:max_r //最大の行index
引数型:int,引数名:min_c //最小の列index
引数型:int,引数名:max_c //最大の列index
matrixのrangeはrow:[min_r..max_r],col:[min_c..max_c],size of row:max_r-min_r+1,size of col:max_c-min_c+1
返却値型:double*,返却値: //領域が確保できた場合,先頭アドレス,確保できなかった場合NULL
備考:matrixの要素へのアクセスはm[row][col]で行う 例えば,最初の要素はm[min_r][min_c]

double **cmatrix(int min_r,int max_r,int min_c,int max_c){
	int i;
	int alloc_flag=0;/*0:success,1:failed*/
	/*行列サイズの計算*/
	int row=max_r-min_r+1;
	int col=max_c-min_c+1;
	if(row<=0 || col<=0){
		printf("error:invalid input indexes\n");/*入力indexが不正です*/
		return NULL;
	}
	/*row個の1次元配列への先頭アドレスを確保)*/
	double **mat=(double **)malloc(sizeof(double*)*row);
	/*確保失敗*/
	if(mat==NULL){
		printf("memory allocation error\n");/*matrix用メモリを確保できませんでした*/
		alloc_flag=1;
		return NULL;
	}
	/*各行について*/
	for(i=0;i<row;i++){
		/*サイズcolの1次元配列確保*/
		mat[i]=(double *)malloc(sizeof(double)*col);
		/*確保失敗*/
		if(mat[i]==NULL){
			printf("memory allocation error\n");/*matrix用メモリを確保できませんでした*/
			alloc_flag=1;
			break;
		}
		/*colについて,最初のindexだけ先頭アドレスをずらす*/
		mat[i]=mat[i]-min_c;
	}
	/*rowについて,最初のindexだけ先頭アドレスをずらす*/
	mat=mat-min_r;
	/*領域確保に失敗した場合,途中まで確保した領域を解放する*/
	if(alloc_flag==1){
		free_cmatrix(mat,min_r,i-1,min_c,max_c);
		return NULL;
	}

	return mat;
}

やはり重要な点は行、列それぞれのアドレスを引数分だけずらすことです。

	/*colについて,最初のindexだけ先頭アドレスをずらす*/
mat[i]=mat[i]-min_c;

	/*rowについて,最初のindexだけ先頭アドレスをずらす*/
mat=mat-min_r;
free_cmatrix

解放処理の場合も同様に、先頭アドレスを生成したときと逆にずらします。

void free_cmatrix(double **mat,int min_r,int max_r,int min_c,int max_c){
	int i;
	/*行列サイズの計算*/
	int row=max_r-min_r+1;
	int col=max_c-min_c+1;
	/*入力アドレスがNULLなら解放処理を行わない*/
	if(mat==NULL){
		return;
	}
	if(row<=0 || col<=0){
		printf("error:invalid input indexes\n");/*入力indexが不正です*/
	}
	/*rowについて最初のindexが0となるように先頭アドレスをずらす*/
	mat=mat+min_r;
	for(i=0;i<row;i++){
		/*colについて最初のindexが0となるように先頭アドレスをずらす*/
		mat[i]=mat[i]+min_c;
		free(mat[i]);
		mat[i]=NULL;
	}
	free(mat);
	mat=NULL;
}
print_matrix

ちなみにprint_matrixはこんな感じです。

void print_cmatrix(double **mat,int min_r,int max_r,int min_c,int max_c){
	int i,j;
	if(mat == NULL){
		printf("error:invalid input\n");
		return;
	}
	for(i=min_r;i<=max_r;i++){
	for(j=min_c;j<=max_c;j++){
		printf("%f ",mat[i][j]);
	}
		printf("\n");
	}
}

行列の計算を行う関数を作る場合にはこのように直接アクセスできるので、とても使いやすいです。

for(i=min_r;i<=max_r;i++){
	for(j=min_c;j<=max_c;j++){
		/*mat[i][j]を使う処理*/
	}
}
cmatrixTest.c

最後に作成した行列を使ってみます。

#include <stdio.h>
#include "cmatrix.h"

int main(){
	/* test cmatrix	*/

	double **A=cmatrix(1,3,2,4);
	A[1][2]=1;	A[1][3]=2;	A[1][4]=3;
	A[2][2]=4;	A[2][3]=5;	A[2][4]=6;
	A[3][2]=7;	A[3][3]=8;	A[3][4]=9;

	print_cmatrix(A,1,3,2,4);

	free_cmatrix(A,1,3,2,4);

	/* test invalid input */

	double **B=cmatrix(4,1,5,0);
	print_cmatrix(B,0,0,0,0);	//error input=NULL
	free_cmatrix(B,0,0,0,0);

	return 0;
}


1.000000 2.000000 3.000000
4.000000 5.000000 6.000000
7.000000 8.000000 9.000000
error:invalid input indexes
error:invalid input