TSV,CSV形式のファイルから二次元配列を読み取る

タブ区切り（TSV)形式、カンマ区切り(CSV)形式の数値ファイルを読み取るプログラムを作ります。いろいろと汎用性が高いものになっています。
##二次元配列のクラス作成
二次元配列とその行数、列数の情報をまとめて扱う二次元配列のクラスを作成します。命名法を工夫するだけでオブジェクト指向ができる[C言語のオブジェクト指向的な記法](https://qiita.com/qiita_kuru/items/8f3441bce9b2f53d1d62)を使います。

構造体を定義して、そのポインタを`typedef`します。構造体を操作する関数（メソッド）は必ずtypedefした構造体のポインタを第一引数として受け取ります。コンストラクタは`クラス名_create` `クラス名_delete`とします。

```c:Array2D.h
typedef struct st_array2d{
	int rows;
	int columns;
	double **data;
}Array2D_Sub;
typedef struct st_array2d *Array2D;

Array2D Array2D_create(int rows, int columns);
void Array2D_delete(Array2D array);
void Array2D_fprint(Array2D array, FILE *fp,char *sep);
void Array2D_print(Array2D array, char *file,char *sep);
//後で実装
Array2D Array2D_read(char *string, ReadOptions options);
```

<details><summary>`Array2D.c`</summary><div>

```c:Array2D.c
#include "Array2D.h"
Array2D Array2D_create(int rows, int columns){
	Array2D array = malloc(sizeof(Array2D_Sub));
	Array2D_init(array,rows,columns);
	return array;
}
void Array2D_delete(Array2D array){
	Array2D_finish(array);
	free(array);
}
void Array2D_fprint(Array2D array, FILE *fp, char *sep){
	int i,j;
	for(i=0;i<array->row;i++){
		for(j=0;j<array->column;j++){
			fprintf(fp,"%g%s",array->data[i][j],sep);
		}
		fprintf(fp,"\n");
	}
}
void Array2D_print(Array2D array, char *file, char *sep){
	FILE *fp;
	
	fp = fopen(file,"w");
	if(!fp){
		perror("Array2D_print : ");
		return;
	}
	Array2D_fprint(array,fp,sep);
	fclose(fp);
}
```
</div></details>
##読み取りオプションの構造体作成
テキストファイルを読み取るときに、いろんなオプションを指定できるようにします。オプションをまとめた構造体`ReadOption`を作ってこれをファイル読み取り関数`Array2D_read`に渡します。

```c++
Array2D Array2D_read(char *string, ReadOption option);
```

##構造体 `ReadOptions`

```c++
#define AUTO -1
#define TRUE 1
#define FALSE 0

typedef struct st_readoptions{
	int rows;
	int columns;
	int rowOffset;
	int columnOffset;
	char **lineSeparators;
	int lineSeparatorsCount;
	char **fieldSeparators;
	int fieldSeparatorsCount;
	int repeatedSeparators;
	int ignoreEmptyLines;
}ReadOptions_Sub;
typedef struct st_readoptions *ReadOptions;
```
`ReadOptions`の各オプションの意味と初期値は以下の表のようになります。

| データ型 | 変数名 | 意味 | 初期値 |
|:--|:------------|:------------|:--|
| int | rows         |行数|AUTO (-1)|
| int | columns      |列数|AUTO (-1)|
| int | rowOffset    |読み取り開始行|0|
| int | columnOffset |読み取り開始列|0|
| char** | lineSeparators | 行区切り文字の配列|NULL|
| int | lineSeparatorsCount | 指定する行区切り文字の個数|0|
| char** | fieldSeparators | 列区切り文字の配列|NULL|
| int | fieldSeparatorsCount |指定する列区切り文字の個数|0|
| int | repeatedSeparators |連続する区切り文字を無視|TRUE(1)|
| int | ignoreEmptyLines |空白行無視|TRUE(1)|

##`ReadOptions`のメソッド
`stdarg.h`を使って可変長引数関数にします。([C言語　可変長引数関数について 【初心者向け】](https://qiita.com/keitean/items/fd2be6710efe681cc516))

```c:Array2D.h
typedef struct st_array2d{
	int rows;
	int columns;
	double **data;
}Array2D_Sub;
typedef struct st_array2d *Array2D;

Array2D Array2D_create(int rows, int columns);
void Array2D_delete(Array2D array);
void Array2D_fprint(Array2D array, FILE *fp,char *sep);
void Array2D_print(Array2D array, char *file,char *sep);
//後で実装
Array2D Array2D_read(char *string, ReadOptions options);

#define AUTO -1
#define TRUE 1
#define FALSE 0
#include <stdarg.h>
void ReadOptions_init(ReadOptions options);
void ReadOptions_finish(ReadOptions options);
void ReadOptions_print(ReadOptions options);
void ReadOptions_setLineSeparators(ReadOptions options, int lineSeparatorsCount, ...);
void ReadOptions_setFieldSeparators(ReadOptions options, int fieldSeparatorsCount, ...);
```

<details><summary>`Array2D.c`</summary><div>

```c:Array2D.c
Array2D Array2D_create(int rows, int columns){
	Array2D array = malloc(sizeof(Array2D_Sub));
	Array2D_init(array,rows,columns);
	return array;
}
void Array2D_delete(Array2D array){
	Array2D_finish(array);
	free(array);
}
void Array2D_init(Array2D array, int rows, int columns){
	int i,j;
	array->rows = rows;
	array->columns = columns;
	array->data = malloc(rows*sizeof(double*));
	array->data[0] = malloc(rows*columns*sizeof(double));
	for(i=0;i<rows;i++){
		array->data[i] = &(array->data[0][i*columns]);
		for(j=0;j<columns;j++){
			array->data[i][j] = 0;
		}
	}
}
void Array2D_fprint(Array2D array, FILE *fp, char *sep){
	int i,j;
	for(i=0;i<array->row;i++){
		for(j=0;j<array->column;j++){
			fprintf(fp,"%g%s",array->data[i][j],sep);
		}
		fprintf(fp,"\n");
	}
}
void Array2D_print(Array2D array, char *file, char *sep){
	FILE *fp;
	
	fp = fopen(file,"w");
	if(!fp){
		perror("Array2D_print : ");
		return;
	}
	Array2D_fprint(array,fp,sep);
	fclose(fp);
}

void ReadOptions_init(ReadOptions options){
	options->rows = AUTO;
	options->columns = AUTO;
	options->rowOffset = 0;
	options->columnOffset = 0;
	options->lineSeparators = NULL;
	options->lineSeparatorsCount = 0;
	options->fieldSeparators = NULL;
	options->fieldSeparatorsCount = 0;
	options->repeatedSeparators = TRUE;
	options->ignoreEmptyLines = FALSE;
}
void ReadOptions_finish(ReadOptions options){
	free(options->lineSeparators);
	free(options->fieldSeparators);
}

void ReadOptions_setLineSeparators(ReadOptions options, int lineSeparatorsCount, ...){
	int i;
	va_list list;
	
	options->lineSeparatorsCount = lineSeparatorsCount;
	options->lineSeparators = malloc(lineSeparatorsCount*sizeof(char*));
	va_start(list,lineSeparatorsCount);
	for(i=0;i<lineSeparatorsCount;i++){
		options->lineSeparators[i] = va_arg(list,char*);
	}
	va_end(list);
}

void ReadOptions_setFieldSeparators(ReadOptions options, int fieldSeparatorsCount, ...){
	int i;
	va_list list;
	
	options->fieldSeparatorsCount = fieldSeparatorsCount;
	options->fieldSeparators = malloc(fieldSeparatorsCount*sizeof(char*));
	va_start(list,fieldSeparatorsCount);
	for(i=0;i<fieldSeparatorsCount;i++){
		options->fieldSeparators[i] = va_arg(list,char*);
	}
	va_end(list);
}
void ReadOptions_print(ReadOptions options){
	int i;
	char *p;
	printf("rows : ");
	if(options->rows < 0){printf("Automatic\n");}
	else{printf("%d\n",options->rows);}
	printf("columns : ");
	if(options->columns < 0){printf("Automatic\n");}
	else{printf("%d\n",options->columns);}
	
	printf("row offset : %d\n",options->rowOffset);
	printf("column offset : %d\n",options->columnOffset);
	printf("line separators : ");
	printf("{");
	for(i=0;i<options->lineSeparatorsCount;i++){
		if(i>0){printf(",");}
		for(p=options->lineSeparators[i];*p;p++){
			switch(*p){
			  case '\r': printf("\\r"); break;
			  case '\n': printf("\\n"); break;
			  case '\t': printf("\\t"); break;
			  default: putchar(*p);
			}
		}
	}
	printf("}\n");
	printf("field separators : ");
	printf("{");
	for(i=0;i<options->fieldSeparatorsCount;i++){
		if(i>0){printf(",");}
		for(p=options->fieldSeparators[i];*p;p++){
			switch(*p){
			  case '\r': printf("\\r"); break;
			  case '\n': printf("\\n"); break;
			  case '\t': printf("\\t"); break;
			  default: putchar(*p);
			}
		}
	}
	printf("}\n");
	printf("repeatedSeparators : ");
	if(options->repeatedSeparators == TRUE){
		printf("True\n");
	}else{
		printf("False\n");
	}
	printf("ignoreEmptyLines : ");
	if(options->ignoreEmptyLines == TRUE){
		printf("True\n");
	}else{
		printf("False\n");
	}
}
```
</div></details>

##`ReadOptions`のテストコード
```c:ReadOptions_test.c
#include "Array2D.h"
int main(int argc, char *argv[]){
	ReadOptions_Sub options;
	
	ReadOptions_init(&options);
	ReadOptions_setLineSeparators(&options,3,"\r","\r\n","\n");
	ReadOptions_setFieldSeparators(&options,3," ","\t");
	ReadOptions_print(&options);
	ReadOptions_finish(&options);
}
```
##`Array2D.h`の予定される使い方
例：CSVファイルの3行目の4列目から2列のデータを読み取る。

```c:ReadCSV.c
#include "Array2D.h"
int main(int argc, char *argv[]){
    char filein[256],*str;
    Array2D array;
    ReadOptions_Sub options;

    if(argc >= 2){
	strncpy(filein,argv[1],256);
    }
    str=String_read(filein);

    ReadOptions_init(&options);
    ReadOptions_setLineSeparators(&options,3,"\n","\r\n","\r"); 
    ReadOptions_setFieldSeparators(&options,1,",");
    options.rowOffset = 3;
    options.columnOffset = 4; 
    options.columns = 2;
    array=Array2D_read(str,&options);
}
```

<details><summary>`String_read`</summary><div>

```c
#include <sys/stat.h>
unsigned long getFileSize(FILE *fp){
	unsigned long fsize = ftell(fp);
	fpos_t p; 
	fgetpos(fp,&p);
	fseek(fp,0,SEEK_END);
	fsize = ftell(fp) - fsize;
	fsetpos(fp,&p);
	return (unsigned long)fsize;
} 

char *String_fread(FILE *fp){
	char *buf;
	char c; unsigned long size;
	size = getFileSize(fp);
	buf = malloc(size+1);
	fread(buf,1,size,fp); buf[size] = '\0';
	return buf;
}

char *String_read(char *file){
	FILE *fp;
	char *str;
	fp = fopen(file,"r");
	if(!fp){perror("String_read"); return NULL;}
	str = String_fread(fp);
	fclose(fp);
	return str;
}
```
</div></details>

##リスト構造
文字列を分割してリスト構造に保存します。
ここで作る`StringList`クラスは文字列のポインタを内容としてもつリスト構造で、リストを削除するとき文字列自体は消去しません。
##`StringList`クラス

```c:StringList.h
typedef struct st_stringlist *StringList;
typedef struct st_stringlist{
	char *string;
	StringList next;
}StringList_Sub;

StringList StringList_create(char *string);
void StringList_delete(StringList list);
void StringList_deleteRoot(StringList list);
StringList StringList_append(StringList list,char *string);
StringList StringList_last(StringList list);
StringList StringList_skip(StringList list, int k);
int StringList_length(StringList list);
StringList StringList_print(StringList list);
char **StringList_toArray(StringList list);
//後で実装
StringList StringList_splitString(char *string, char **separators, int separatorsCount, int repeatedSeparators);
```

<details><summary>`StringList.c`</summary><div>

```c:StringList.c
#include "StringList.h"
StringList StringList_create(char *string){
	StringList list;
	list = malloc(sizeof(StringList_Sub));
	list->string = string;
	list->next = NULL;
	return list;
}

void StringList_delete(StringList list){free(list);}
void StringList_deleteRoot(StringList list){
	if(list){StringList_deleteRoot(list->next);};
	StringList_delete(list);
}

StringList StringList_last(StringList list){
	for(;list->next;list=list->next){}
	return list;
}

StringList StringList_append(StringList list,char *string){
	if(list){
		StringList_last(list)->next = StringList_create(string);
		return list;
	}else{
		return StringList_create(string);
	}
}

StringList StringList_skip(StringList list, int k){
	int i;
	for(i=0;i<k && list;list=list->next,i++){}
	return list;
}

StringList StringList_print(StringList list){
	if(list){
		for(;list;list=list->next){
			printf("%s\n",list->string);
		}
	}
}

int StringList_length(StringList list){
	int count=0;
	for(;list;list=list->next){count++;}
	return count;
}

char **StringList_toArray(StringList list){
	int i,len;
	char **array;
	len = StringList_length(list);
	array = malloc(len*sizeof(char*));
	for(i=0;i<len;i++,list++){
		array[i] = list->string;
	}
}
```
</div></details>

## `StringList`のテストコード

```c:StringList_test.c
#include "StringList.h"
int main(int argc, char *argv[]){
	StringList sl = StringList_create("ant");
	StringList_append(sl,"bear");
	StringList_append(sl,"cat");
	printf("length:%d\n",StringList_length(sl));
	StringList_print(sl);
	StringList_deleteRoot(sl);
}	
```
##`StringList_splitString`
区切り文字によって文字列を分割する関数を作ります。

```c:StringList.h
StringList StringList_splitString(char *string, char **separators, int separatorsCount, int repeatedSeparators);
```
`string`はもとの文字列、`separators`は区切り文字のリスト、`separatorsCount`は区切り文字の数、`repeatedSeparators`は連続する区切り文字を無視するかどうかのフラグです。
仕様としてこの関数は、入力の文字列に対して区切り文字がある位置を`\0`で入れ替えるという破壊的処理を行います。なので入力の文字列は必ず`malloc`などでメモリ上に確保した領域上のものである必要があります。
分割された文字列のメモリをいちいち確保するのは非効率なので、このような仕様にしました。

```c:StringList.c
StringList StringList_splitString(char *string, char **separators, int separatorsCount,int repeatedSeparators){
	int i,imin,len;
	char *p,*p2,*last,*pmin;
	StringList list=NULL;

	/*文字列の最後尾のポインタをlastに保存*/
	last = strchr(string,'\0'); 
	/*ポインタpをstringの先頭から最後尾lastまで移動させていく*/
	/*p2が新しい位置*/
	for(p=string;p<last;p=p2){
		/*区切り文字の数だけ繰り返す*/
		for(i=0,pmin=last,imin=-1;i<separatorsCount;i++){
			/*区切り文字を検索して、p2にその位置を保存*/
			p2=strstr(p,separators[i]);
			/*区切り文字がない場合p2にはNULLが入る*/
			if(p2){
				/*見つかった区切り文字のうち、最も検索開始位置に近い位置を記憶*/
				if(p2 < pmin){
					imin = i;
					pmin = p2;
				}
			}
		}
		/*p2は次の検索の開始点となる。*/
		if(imin<0){
			/*区切り文字がみつからなかった場合。*/
			p2 = last;
		}else{
			/*区切り文字がみつかった場合*/
			len = strlen(separators[imin]);
			/*区切り文字を\0で置き換える*/
			for(i=0;i<len;i++){pmin[i] = '\0';}
			/*区切り文字の次の文字列をp2にいれる。*/
			p2 = &pmin[i];
		}
		/*pが\0でないか、repeatedSeparators==FALSEのときリストに文字列を追加する。*/
		/*repeatedSeparators==FALSEのとき、リストには空文字\0が入る。*/
		if(*p || !repeatedSeparators){list=StringList_append(list,p);}
	}
	return list;
}
```
##`Array2D_read`
ファイルから二次元データを読み取る関数です。

```c:Array2D.c
Array2D Array2D_read(char *string, ReadOptions options){
	StringList list,s,*listlist;
	int i,j,rows,columns,rofs,cofs,c;
	Array2D array=NULL;
	
	/*まず行区切り文字で分割する。*/
	list = StringList_splitString(string,options->lineSeparators,options->lineSeparatorsCount,options->ignoreEmptyLines);
	rows = StringList_length(list);
	
	/*行オフセットにマイナスの値を入れたらオフセットは＿最後尾から数える。*/
	if(options->rowOffset < 0){
		rofs = rows + options->rowOffset;
	}else{
		rofs = options->rowOffset;
	}
	
	/*行数にマイナスの値を入れたら全行読み取る*/
	if(options->rows < 0){
		if(rows < rofs){
			printf("Array2D_read : row offset %d exceeds the row count %d\n",rofs,rows);
			goto end;
		}
		rows = rows - rofs;
	}else{
		if(rows < options->rows + rofs){
			printf("Array2D_read : rows %d + row offset %d exceeds input data rows %d\n",
				   options->rows,rofs,rows);
		}
		rows = options->rows;
	}

	/*行数ぶんのStringList配列を確保する。*/
	listlist = malloc(rows*sizeof(StringList));
	for(i=0,columns=0,s=StringList_skip(list,rofs);i<rows && s;i++,s=s->next){
		/*各行について、列区切り文字で分割する。*/
		listlist[i] = StringList_splitString(s->string,options->fieldSeparators,options->fieldSeparatorsCount,options->repeatedSeparators);
		/*最大の列数をcに格納する*/
		c = StringList_length(listlist[i]);
		if(columns < c){columns=c;}
	}
	
	/*列数、列オフセットについても行と同じ処理を行う。*/
	if(options->columnOffset < 0){
		cofs = columns + options->columnOffset;
	}else{
		cofs = options->columnOffset;
	}
	
	if(options->columns < 0){
		if(columns < cofs){
			printf("Array2D_read : column offset %d exceeds the input data columns%d\n",cofs,columns);
			goto end;
		}
		columns = columns - cofs;
	}else{
		if(columns < options->columns + cofs){
			printf("Array2D_read : columns %d + column offset %d exceeds input data columns %d\n",
				   options->columns,cofs,columns);
		}
		columns = options->columns;
	}
	
	array = Array2D_create(rows,columns);
	
	for(i=0;i<rows;i++){
		/*列オフセットだけリストをスキップする*/
		for(j=0,s=StringList_skip(listlist[i],cofs);j<columns && s;j++,s=s->next){
			array->data[i][j] = atof(s->string);
		}
	}
  end:
	/*リストを削除する。*/
	for(i=0;i<rows;i++){StringList_deleteRoot(listlist[i]);}
	free(listlist);
	StringList_deleteRoot(list);
	
	return array;
}

```
##ファイルから二次元配列を読み取るプログラム
これまで作ったクラスを使って目的のプログラムを完成させます。
ファイルから配列を読み取って表示するだけのわりに、汎用性を高めてあるのでわりとボリューム感があります。
完成です。ありがとうございました。

```c:readmat.c
#include "utilities.h"
#include "Array2D.h"
#include "StringList.h"
#include <getopt.h>

int main(int argc, char *argv[]){
	char filein[256],fileout[256],*str;
	int mode=0;
	int ch,len;
	extern char *optarg;
	extern int optind;
	
	Array2D array;
	StringList l_list=NULL,f_list=NULL;
	ReadOptions_Sub options;
	
	ReadOptions_init(&options);
	struct option longopts[]=
			{
			 {"rofs",required_argument,NULL,'r'},
			 {"cofs",required_argument,NULL,'c'},
			 {"row",required_argument,NULL,'R'},
			 {"col",required_argument,NULL,'C'},
			 {"rsep",required_argument,NULL,'l'},
			 {"csep",required_argument,NULL,'f'},
			 {0,0,0,0}
			 };
	int opt,longindex;
	
	while((opt=getopt_long(argc,argv,"r:c:R:C:l:f:",longopts,&longindex))!=-1){
		switch(opt){
		  case 'R':
			options.rowOffset = atoi(optarg); break;
		  case 'C':
			options.columnOffset = atoi(optarg); break;
		  case 'r':
			options.rows = atoi(optarg); break;
		  case 'c':
			options.columns = atoi(optarg); break;
		  case 'l':
			l_list = StringList_append(l_list,optarg); break;
		  case 'f':
			f_list = StringList_append(f_list,optarg); break;
		  default:
			break;
		}
	}
	
	if(optind < argc){
		strncpy(filein,argv[optind],256);
	}else{
		puts("input file name");
		readFromStdin(filein,256);
	}
	
	str=String_read(filein);
	if(l_list){
		options.lineSeparatorsCount = StringList_length(l_list);
		options.lineSeparators = StringList_toArray(l_list);
	}else{
		ReadOptions_setLineSeparators(&options,3,"\n","\r\n","\r");
	}
	if(f_list){
		options.fieldSeparatorsCount = StringList_length(f_list);
		options.fieldSeparators = StringList_toArray(f_list);
	}else{
		ReadOptions_setFieldSeparators(&options,2," ","\t");
	}
	StringList_delete(f_list);
	StringList_delete(l_list);
	
	//ReadOptions_print(&options);
	array=Array2D_read(str,&options);
	//printf("array->rows : %d\n",array->rows);
	//printf("array->columns : %d\n",array->columns);
	Array2D_fprint(array,stdout," ");
	Array2D_delete(array);
	ReadOptions_finish(&options);
	String_delete(str);
	return 0;
}

```

```c:utilities.h
#ifndef UTILITIES_H
#define UTILITIES_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
int readFromStdin(char *str,int size);
unsigned long getFileSize(FILE *fp);
char *String_fread(FILE *fp);
char *String_read(char *file);
void *String_delete(char *str);
#endif
```

<details><summary>`utilities.c`</summary><div>

```c:utilities.c
#include "utilities.h"

unsigned long getFileSizeBin(const char *filename){
	struct stat st;
	if(stat(filename,&st)==0){return (unsigned long)st.st_size;}
	return 0;
}

unsigned long getFileSize(FILE *fp){
	unsigned long fsize = ftell(fp);
	fpos_t p; 
	fgetpos(fp,&p);
	fseek(fp,0,SEEK_END);
	fsize = ftell(fp) - fsize;
	fsetpos(fp,&p);
	return (unsigned long)fsize;
} 

char *String_fread(FILE *fp){
	char *buf;
	char c; unsigned long size;
	size = getFileSize(fp);
	buf = malloc(size+1);
	fread(buf,1,size,fp); buf[size] = '\0';
	return buf;
}

char *String_read(char *file){
	FILE *fp;
	char *str;
	fp = fopen(file,"r");
	if(!fp){perror("String_read"); return NULL;}
	str = String_fread(fp);
	fclose(fp);
	return str;
}

void *String_delete(char *str){free(str);}

int readFromStdin(char *str,int size){
	int len;
	fgets(str,size,stdin);
	len=strlen(str);
	if(str[len-1] != '\n'){
		scanf("%*s");
		return -1;
	}else{
		str[len-1]='\0';
	}
	return len;
}
```
</div></details>

```c:Array2D.h
#ifndef ARRAY2D_H
#define ARRAY2D_H
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <stdarg.h>
#include <math.h>

#define AUTO -1
#define TRUE 1
#define FALSE 0

typedef struct st_array2d{
	int rows;
	int columns;
	double **data;
}Array2D_Sub;
typedef struct st_array2d *Array2D;

typedef struct st_readoptions{
	int rows;
	int columns;
	int rowOffset;
	int columnOffset;
	char **lineSeparators;
	int lineSeparatorsCount;
	char **fieldSeparators;
	int fieldSeparatorsCount;
	int repeatedSeparators;
	int ignoreEmptyLines;
}ReadOptions_Sub;
typedef struct st_readoptions *ReadOptions;

Array2D Array2D_create(int rows, int coulmns);
void Array2D_delete(Array2D array);
void Array2D_fprint(Array2D array, FILE *fp,char *sep);
void Array2D_print(Array2D array, char *file,char *sep);
Array2D Array2D_read(char *string, ReadOptions option);

void ReadOptions_init(ReadOptions options);
void ReadOptions_finish(ReadOptions options);
void ReadOptions_print(ReadOptions options);
void ReadOptions_setLineSeparators(ReadOptions options, int lineSeparatorsCount, ...);
void ReadOptions_setFieldSeparators(ReadOptions options, int fieldSeparatorsCount, ...);
#endif
```
<details><summary>`Array2D.c`</summary><div>

```c:Array2D.c
#include "Array2D.h"
#include "StringList.h"

void ReadOptions_init(ReadOptions options){
	options->rows = AUTO;
	options->columns = AUTO;
	options->rowOffset = 0;
	options->columnOffset = 0;
	options->lineSeparators = NULL;
	options->lineSeparatorsCount = 0;
	options->fieldSeparators = NULL;
	options->fieldSeparatorsCount = 0;
	options->repeatedSeparators = TRUE;
	options->ignoreEmptyLines = FALSE;
}
void ReadOptions_finish(ReadOptions options){
	free(options->lineSeparators);
	free(options->fieldSeparators);
}

void ReadOptions_setLineSeparators(ReadOptions options, int lineSeparatorsCount, ...){
	int i;
	va_list list;
	
	options->lineSeparatorsCount = lineSeparatorsCount;
	options->lineSeparators = malloc(lineSeparatorsCount*sizeof(char*));
	va_start(list,lineSeparatorsCount);
	for(i=0;i<lineSeparatorsCount;i++){
		options->lineSeparators[i] = va_arg(list,char*);
	}
	va_end(list);
}

void ReadOptions_setFieldSeparators(ReadOptions options, int fieldSeparatorsCount, ...){
	int i;
	va_list list;
	
	options->fieldSeparatorsCount = fieldSeparatorsCount;
	options->fieldSeparators = malloc(fieldSeparatorsCount*sizeof(char*));
	va_start(list,fieldSeparatorsCount);
	for(i=0;i<fieldSeparatorsCount;i++){
		options->fieldSeparators[i] = va_arg(list,char*);
	}
	va_end(list);
}

void ReadOptions_print(ReadOptions options){
	int i;
	char *p;
	printf("rows : ");
	if(options->rows < 0){printf("Automatic\n");}
	else{printf("%d\n",options->rows);}
	printf("columns : ");
	if(options->columns < 0){printf("Automatic\n");}
	else{printf("%d\n",options->columns);}
	
	printf("row offset : %d\n",options->rowOffset);
	printf("column offset : %d\n",options->columnOffset);
	printf("line separators : ");
	printf("{");
	for(i=0;i<options->lineSeparatorsCount;i++){
		if(i>0){printf(",");}
		for(p=options->lineSeparators[i];*p;p++){
			switch(*p){
			  case '\r': printf("\\r"); break;
			  case '\n': printf("\\n"); break;
			  case '\t': printf("\\t"); break;
			  default: putchar(*p);
			}
		}
	}
	printf("}\n");
	printf("field separators : ");
	printf("{");
	for(i=0;i<options->fieldSeparatorsCount;i++){
		if(i>0){printf(",");}
		for(p=options->fieldSeparators[i];*p;p++){
			switch(*p){
			  case '\r': printf("\\r"); break;
			  case '\n': printf("\\n"); break;
			  case '\t': printf("\\t"); break;
			  default: putchar(*p);
			}
		}
	}
	printf("}\n");
	printf("repeatedSeparators : ");
	if(options->repeatedSeparators == TRUE){
		printf("True\n");
	}else{
		printf("False\n");
	}
	printf("ignoreEmptyLines : ");
	if(options->ignoreEmptyLines == TRUE){
		printf("True\n");
	}else{
		printf("False\n");
	}
}

Array2D Array2D_read(char *string, ReadOptions options){
	StringList list,s,*listlist;
	int i,j,rows,columns,rofs,cofs,c;
	Array2D array=NULL;
	
	list = StringList_splitString(string,options->lineSeparators,options->lineSeparatorsCount,options->ignoreEmptyLines);
	rows = StringList_length(list);
	
	if(options->rowOffset < 0){
		rofs = rows + options->rowOffset;
	}else{
		rofs = options->rowOffset;
	}
	
	if(options->rows < 0){
		if(rows < rofs){
			printf("Array2D_read : row offset %d exceeds the row count %d\n",rofs,rows);
			goto end;
		}
		rows = rows - rofs;
	}else{
		if(rows < options->rows + rofs){
			printf("Array2D_read : rows %d + row offset %d exceeds input data rows %d\n",
				   options->rows,rofs,rows);
		}
		rows = options->rows;
	}

	listlist = malloc(rows*sizeof(StringList));
	for(i=0,columns=0,s=StringList_skip(list,rofs);i<rows && s;i++,s=s->next){
		listlist[i] = StringList_splitString(s->string,options->fieldSeparators,options->fieldSeparatorsCount,options->repeatedSeparators);
		c = StringList_length(listlist[i]);
		if(columns < c){columns=c;}
	}
	
	if(options->columnOffset < 0){
		cofs = columns + options->columnOffset;
	}else{
		cofs = options->columnOffset;
	}
	
	if(options->columns < 0){
		if(columns < cofs){
			printf("Array2D_read : column offset %d exceeds the input data columns%d\n",cofs,columns);
			goto end;
		}
		columns = columns - cofs;
	}else{
		if(columns < options->columns + cofs){
			printf("Array2D_read : columns %d + column offset %d exceeds input data columns %d\n",
				   options->columns,cofs,columns);
		}
		columns = options->columns;
	}
		
	array = Array2D_create(rows,columns);
	
	for(i=0;i<rows;i++){
		for(j=0,s=StringList_skip(listlist[i],cofs);j<columns && s;j++,s=s->next){
			array->data[i][j] = atof(s->string);
		}
	}
  end:
	for(i=0;i<rows;i++){StringList_deleteRoot(listlist[i]);}
	free(listlist);
	StringList_deleteRoot(list);
	
	return array;
}

void Array2D_fprint(Array2D array, FILE *fp, char *sep){
	int i,j;
	for(i=0;i<array->rows;i++){
		for(j=0;j<array->columns;j++){
			fprintf(fp,"%g%s",array->data[i][j],sep);
		}
		fprintf(fp,"\n");
	}
}

void Array2D_print(Array2D array, char *file, char *sep){
	FILE *fp;
	
	fp = fopen(file,"w");
	if(!fp){
		perror("Array2D_print : ");
		return;
	}
	Array2D_fprint(array,fp,sep);
	fclose(fp);
}

Array2D Array2D_create(int rows, int columns){
	Array2D array = malloc(sizeof(Array2D_Sub));
	Array2D_init(array,rows,columns);
	return array;
}

void Array2D_delete(Array2D array){
	Array2D_finish(array);
	free(array);
}

```
</div></details>

```c:StringList.h
#ifndef STRINGLIST_H
#define STRINGLIST_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct st_stringlist *StringList;
typedef struct st_stringlist{
	char *string;
	StringList next;
}StringList_Sub;

StringList StringList_print(StringList list);
StringList StringList_create(char *string);
StringList StringList_append(StringList list,char *string);
StringList StringList_last(StringList list);
StringList StringList_skip(StringList list, int k);
int StringList_length(StringList list);
char **StringList_toArray(StringList list);

void StringList_delete(StringList list);
void StringList_deleteRoot(StringList list);
StringList StringList_splitString(char *string, char **separators, int separatorsCount, int repeatedSeparators);
#endif
```

<details><summary>`StringList.c`</summary><div>

```c:StringList.c
StringList StringList_create(char *string){
	StringList list;
	list = malloc(sizeof(StringList_Sub));
	list->string = string;
	list->next = NULL;
	return list;
}

void StringList_delete(StringList list){free(list);}
void StringList_deleteRoot(StringList list){
	if(list){StringList_deleteRoot(list->next);};
	StringList_delete(list);
}

StringList StringList_last(StringList list){
	for(;list->next;list=list->next){}
	return list;
}

StringList StringList_append(StringList list,char *string){
	if(list){
		StringList_last(list)->next = StringList_create(string);
		return list;
	}else{
		return StringList_create(string);
	}
}

StringList StringList_skip(StringList list, int k){
	int i;
	for(i=0;i<k && list;list=list->next,i++){}
	return list;
}

StringList StringList_print(StringList list){
	if(list){
		for(;list;list=list->next){
			printf("%s\n",list->string);
		}
	}
}

int StringList_length(StringList list){
	int count=0;
	for(;list;list=list->next){count++;}
	return count;
}

char **StringList_toArray(StringList list){
	int i,len;
	char **array;
	len = StringList_length(list);
	array = malloc(len*sizeof(char*));
	for(i=0;i<len;i++,list++){
		array[i] = list->string;
	}
}

StringList StringList_splitString(char *string, char **separators, int separatorsCount,int repeatedSeparators){
	int i,imin,len;
	char *p,*p2,*last,*pmin;
	StringList list=NULL;
	
	last = strchr(string,'\0');
	for(p=string;p<last;p=p2){
		for(i=0,pmin=last,imin=-1;i<separatorsCount;i++){
			p2=strstr(p,separators[i]);
			if(p2){
				if(p2 < pmin){
					imin = i;
					pmin = p2;
				}
			}
		}
		if(imin<0){
			p2 = last;
		}else{
			len = strlen(separators[imin]);
			for(i=0;i<len;i++){pmin[i] = '\0';}
			p2 = &pmin[i];
		}
		if(*p || !repeatedSeparators){list=StringList_append(list,p);}
	}
	return list;
}
```
</div></details>