CSV ファイルを C 言語で読み込む
どうも,筆者です.
今回は,CSV ファイルを C 言語で読み込むことを考える.
考え方
カンマ(,)か改行コード(\n)が出てくるまで,データを char 型の配列に保持する.カンマ(,)か改行コード(\n)が出てきたら,atoi または,atof 関数で数値に変換する. この時,行数と列数が分からないので,一度ファイルの終端コード(EOF)が出てくるまで読み続ける.その後,先頭に戻って再度処理をするという流れになる.
プログラム
まず,戻り値の処理をするため,ヘッダに列挙型を定義する.
/* defineConstant.h */ #ifndef __DEFINECONSTANT_H__ #define __DEFINECONSTANT_H__ typedef enum { RETURN_OK = 0, ARGUMENT_ERROR, FILE_OPEN_ERROR, MALLOC_ERROR } RETURN_TYPE; #endif
次に,行数と列数をカウントするプログラムを作成する.
#include"defineConstant.h" /* iCountRowCol: 行数と列数をカウントする char *pszFileName: 読み込むファイル名 unsigned int *piRow: 行数 unsigned int *piCol: 列数 return: 正常終了 -> 0 異常終了 -> 0 以外 */ int iCountRowCol(char *pszFileName, unsigned int *puiRow, unsigned int *puiCol){ FILE *prFile; int iRet; unsigned int uiRow, uiCol; char chData; iRet = RETURN_OK; /* 引数のチェック */ if((NULL == pszFileName) || (NULL == puiRow) || (NULL == puiCol)){ fprintf(stderr, "argument error\n"); iRet = ARGUMENT_ERROR; goto EXIT_COUNT_ROWCOL; } else{ *puiRow = *puiCol = 0; } if(NULL == (prFile = fopen(pszFileName, "r"))){ fprintf(stderr, "Error: %s cannot open\n", pszFileName); iRet = FILE_OPEN_ERROR; goto EXIT_COUNT_ROWCOL; } uiRow = uiCol = 0; while(EOF != fscanf(prFile, "%c", &chData)){ /* カンマの場合 */ if(',' == chData){ uiCol++; } /* 改行コードの場合 */ if('\n' == chData){ uiCol++; uiRow++; } } uiCol /= uiRow; *puiRow = uiRow; *puiCol = uiCol; fclose(prFile); EXIT_COUNT_ROWCOL: return iRet; }
その後,必要なサイズを malloc する関数を定義する.ここでは,int 型で確保する.同時に,領域を開放する関数も書く.
/* iMallocInt: int 型の malloc を行う unsigned int uiSize: 確保するサイズ int **ppiArray: 確保先のアドレス return: 正常終了 -> 0 異常終了 -> 0 以外 */ int iMallocInt(unsigned int uiSize, int **ppiArray){ unsigned int uiCnt; int iRet; iRet = RETURN_OK; /* 引数チェック */ if(NULL == ppiArray){ iRet = ARGUMENT_ERROR; goto EXIT_MALLOC_INT; } /* malloc */ if(NULL == (*ppiArray = (int *)malloc(sizeof(int) * uiSize))){ fprintf(stderr, "malloc error\n"); iRet = MALLOC_ERROR; goto EXIT_MALLOC_INT; } else{ for(uiCnt = 0; uiCnt < uiSize; uiCnt++){ (*ppiArray)[uiCnt] = 0; } } EXIT_MALLOC_INT: return iRet; } /* freeData: 確保した領域を開放する void *pvData: 確保した領域の先頭アドレス */ void freeData(void *pvData){ if(NULL != pvData){ free(pvData); } return; }
最後に,データを解析し,読み込んでいく.
/* iReadData: データの読み込み char *pszFileName: 読み込むファイル名 unsigned int uiRow: ファイルの行数 unsigned int uiCol: ファイルの列数 int *piArray: データの格納先 return: 正常終了 -> 0 異常終了 -> 0 以外 */ int iReadData(char *pszFileName, unsigned int uiRow, unsigned int uiCol, int *piArray){ FILE *prFile; char chData; int iRet; unsigned int uiCntRow, uiCntCol, uiCharCnt; char achInputDataList[11]; iRet = RETURN_OK; /* 引数のチェック */ if(NULL == pszFileName){ iRet = ARGUMENT_ERROR; goto EXIT_READ_DATA; } /* ファイルオープン */ if(NULL == (prFile = fopen(pszFileName, "r"))){ fprintf(stderr, "Error: %s cannot open\n", pszFileName); iRet = FILE_OPEN_ERROR; goto EXIT_READ_DATA; } /* ファイルの読み込み */ for(uiCntRow = 0; uiCntRow < uiRow; uiCntRow++){ for(uiCntCol = 0; uiCntCol < uiCol; uiCntCol++){ uiCharCnt = 0; achInputDataList[0] = '\0'; /* 1 文字ずつ読み込む */ while(EOF != fscanf(prFile, "%c", &chData)){ /* 「,」または「\n」が来た場合,ループを抜ける */ if((',' == chData) || ('\n' == chData)){ break; } /* 今回は,数値の部分だけ保存 */ if(((int)'0' <= (int)chData) && ((int)chData <= (int)'9')){ achInputDataList[uiCharCnt++] = chData; } } achInputDataList[uiCharCnt] = '\0'; piArray[uiCntRow * uiCol + uiCntCol] = atoi(achInputDataList); } } fclose(prFile); EXIT_READ_DATA: return iRet; }
これで,CSV ファイルからデータを読み込み,格納することができる.後は,これをまとめて行う関数を定義する.
/* iReadCSVFile: CSV ファイルからデータを読み込む char *pszFileName: 読み込むファイル名 unsigned int *piRow: 行数 unsigned int *piCol: 列数 int **ppiArray: 確保先のアドレス return: 正常終了 -> 0 異常終了 -> 0 以外 */ int iReadCSVFile(char *pszFileName, unsigned int *puiRow, unsigned int *puiCol, int **ppiArray){ int iRet; unsigned int uiSize = 0; iRet = iCountRowCol(pszFileName, puiRow, puiCol); if(RETURN_OK != iRet){ fprintf(stderr, "Error: iCountRowCol\n"); goto EXIT_READ_CSV_FILE; } uiSize = (*puiRow) * (*puiCol); iRet = iMallocInt(uiSize, ppiArray); if(RETURN_OK != iRet){ fprintf(stderr, "Error: iMallocInt\n"); goto EXIT_READ_CSV_FILE; } iRet = iReadData(pszFileName, *puiRow, *puiCol, *ppiArray); if(RETURN_OK != iRet){ fprintf(stderr, "Error: iReadData\n"); goto EXIT_READ_CSV_FILE; } EXIT_READ_CSV_FILE: return iRet; }
確認
このファイルを readCSVFile.c として保存し,正しく動作するか確認する.main 関数は,以下のようにした.
#include<stdio.h> #include<stdlib.h> #include"defineConstant.h" extern int iReadCSVFile(char *pszFileName, unsigned int *puiRow, unsigned int *puiCol, int **ppiArray); extern void freeData(void *pvData); void printData(unsigned int uiRow, unsigned int uiCol, int *piArray); int main(int iArgCnt, char **ppchArgVec){ int iRet; unsigned int uiRow, uiCol; int *piArray; if(iArgCnt == 2){ iRet = iReadCSVFile(ppchArgVec[1], &uiRow, &uiCol, &piArray); if(iRet == RETURN_OK){ printData(uiRow, uiCol, piArray); freeData((void *)piArray); } } return 0; } void printData(unsigned int uiRow, unsigned int uiCol, int *piArray){ unsigned int uiCntRow, uiCntCol; for(uiCntRow = 0; uiCntRow < uiRow; uiCntRow++){ for(uiCntCol = 0; uiCntCol < uiCol; uiCntCol++){ printf("%d ", piArray[uiCntRow * uiCol + uiCntCol]); } printf("\n"); } }
CSV ファイルは,以下のようなものを作成した.
01,02,03,04,05,06,07,08,09,10 11,12,13,14,15,16,17,18,19,20 21,22,23,24,25,26,27,28,29,30 31,32,33,34,35,36,37,38,39,40 41,42,43,44,45,46,47,48,49,50 51,52,53,54,55,56,57,58,59,60 61,62,63,64,65,66,67,68,69,70 71,72,73,74,75,76,77,78,79,80 81,82,83,84,85,86,87,88,89,90 91,92,93,94,95,96,97,98,99,100
実行結果は,以下のようになった.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
正しく動作している.
楕円が存在する領域
どうも,筆者です.
楕円を覆う領域
今回は,有限個の楕円が平面状に散らばっている状況を考える.この状況において,すべての楕円を含む長方形領域の最小値を求める問題を解く. 図で示すと以下のような長方形領域を求めることになる.
領域の計算方法
計算方法として,以下のような楕円ごとの長方形の領域を考えた.
この図において,楕円の方程式は,
で与えられる.ここで, である.また,媒介変数 を用いると,楕円上の点 は,
となる.
この時,楕円を だけ回転させた場合,移動後の点 は,回転行列を用いて,
となる.さらに,三角関数の合成を用いて,
と表せる.ここで,
である.
よって, がそれぞれ最大となる時が,求める長方形領域の半分の長さとなるので, は,
となる.
C プログラム
後は,この結果をプログラムに落とすだけである.ここでは,以下のような図を考える.
この図のように,それぞれの楕円に外接する長方形を計算し,原点から最も遠くなる位置を求める.以下に,プログラムを示す.
これで,図に示した青色の領域を求めることができる.
関連記事のサムネイルを表示する
どうも,筆者です.
以前,ブログの見た目を変更しようと思い,関連記事を実装した.その際,サムネイル画像が表示されなかったが,先日,修正方法が記事になっていた.
こちらを参考に再度修正を行った.
ヘッダの修正
Java Script は,普段使わないので,間違えないように修正する.以下は,上記のサイトの一部引用である.
var title = el.find('title').text(); //ブログタイトル var articleLink = el.find('link').attr('href'); //ブログタイトル var overview = el.find('summary').text(); //ブログ概要
そして,記事を参考に,imgSrc を articleLink の下に挿入する.後で見たときに分かるように,ブログイメージとコメントを入れておいた.
var title = el.find('title').text(); //ブログタイトル var articleLink = el.find('link').attr('href'); //ブログタイトル var imgSrc = el.find('link[type="image/png"]').attr('href'); //ブログイメージ ←追加部分 var overview = el.find('summary').text(); //ブログ概要
最後に,html の部分をサイトの通り,書き換える.この部分は,サイトのものをそのままコピペした.
変更後
デザインの変更をした結果,以下のようになった.
修正したことで,問題なく動作しているようだ.良かった.
ブログの見た目を改善
どうも,筆者です.
ブログの見た目を変更していると,あれも欲しいこれも欲しいって思うようになってくる.ということで,他のすばらしいサイトを参考にしながら,設定を行う.
関連記事
色々なブログを見ていると,関連記事が表示されていることに気付く.これは,設定したいと思い,調べた.以下のサイトで,コードが公開されていたので,記事の数だけ変更して利用させていただいた.
この時,何故か目次の設定(自動生成してくれる JS)が,「記事の下」に HTML で記述されていたので,「記事の上」に移動させておいた.
アイキャッチ画像(サムネイル画像)
関連記事を導入したところで,多少便利になったが,サムネイル画像が表示されないことに気付いた.これも設定していなかったようなので,以下の記事を参考にしながら,設定を行った.
happylife-tsubuyaki.hatenablog.com
サムネイル画像は Inkscape で作成し,アップロードすることにした.ただ,上手に作れないのが難点である.
Raspberry Pi 3 Model B のプリンタサーバーにおいて Windows から PDF が印刷できない問題とその対処法
どうも,筆者です.
Windows で PDF の印刷をする
前回までで,Raspberry Pi をファイルサーバーとプリンタサーバーにできた.問題なく動作していたのだが,Windows で PDF の印刷が出来なかった.Word や Excel は印刷できたのに,PDF だけ印刷できなかった.原因がさっぱり分からない.また,Linux 側では,PDF が印刷できたので,プリンタサーバーとしては動作していると考えた.面倒なことに,Windows 側での印刷の仕組みを勉強する必要が生じた.
ログの出力
Windows で Word を印刷する場合と PDF を印刷する場合の違いを調べるために,ログを見ることにした.Windows はとても親切なので,ログが一切残っていなかった.まずは,ログの残し方から調べる必要があった.
調べるとすぐ出てきた.「イベントビューアー」というものがあるらしい.これで,ログの有効化を行う.Windows 7 の場合は,
- 管理ツールを開く(コントロールパネルにある「システムとセキュリティ」から飛ぶ).
- イベントビューアーを起動する.
- 「アプリケーションとサービス\Microsoft\Windows\PrintService\Operational」を開く.
- 右側にある「操作」から,「ログの有効化」をクリックする.
とすれば良い.
その後,「アプリケーションとサービス\Microsoft\Windows\PrintService\Admin」から,Word を印刷した場合(これは実行可能)のログを見る.問題ない.その後,PDF を印刷した場合(これはエラーを返す)のログを見る.すると,
%UserName% によって所有されているドキュメント %DataFile% をプリンター %PrinterName% で印刷できませんでした。ドキュメントを再度印刷するか、印刷スプーラーを再起動してください。 データの種類: RAW。スプール ファイルのサイズ (バイト): xxx。印刷したバイト数: yyy。ドキュメントの総ページ数: zzz。印刷したページ数: xyz。クライアントのコンピューター: %PCName%。プリント プロセッサで返された Win32 エラー コード: 5。アクセスが拒否されました。
となっていた.アクセスが拒否されているらしい.
アクセス拒否の対処法
これは, TS Easy PrintでイベントID:6161アクセス拒否の件、解決ダー Σ∩グッkametaro.wordpress.com
によると,
直接の原因は、RemoteAppログインに使用したユーザーが、サーバー側のスプールフォルダにアクセスできる権限を持っていないため。
とある.つまり,サーバ側の spool ディレクトリにアクセスできないとのことらしい.という事は,Samba の設定ファイルのうち,
[printers] comment = All Printers browseable = no path = /var/spool/samba printable = yes guest ok = no read only = yes create mask = 0700
を
[printers] comment = All Printers browseable = no path = /var/spool/samba printable = yes guest ok = yes ## <- ここ read only = yes create mask = 0700
とすればよいはずである.変更してサービスを再起動し,再度 Windows から PDF の印刷を行うと,印刷できた.これで,問題は解決された.
現状できていること
現状は,ファイルサーバー,プリンタサーバーに加え,スマホから「reboot」と「shutdown」ができるようになっている.他にも色々機能を付けたいと思う.