K&R第2版から遡ること10年、K&R初版が出版された1978年当時のC言語コードいわゆるpre K&Rコードをコンパイル&実行してみました。
手順
1. K&R初版訳本P87のgetch関数ファイルをgetch.cとする。
2. K&R初版訳本P101のgetint関数ファイルをgetint.cとする。
3. main.cを作成する(過去記事[C言語]03参照)。
4. main.cをコンパイルし、a.outを実行する。
K&R第2版から遡ること10年、K&R初版が出版された1978年当時のC言語コードいわゆるpre K&Rコードをコンパイル&実行してみました。
手順
1. K&R初版訳本P87のgetch関数ファイルをgetch.cとする。
2. K&R初版訳本P101のgetint関数ファイルをgetint.cとする。
3. main.cを作成する(過去記事[C言語]03参照)。
4. main.cをコンパイルし、a.outを実行する。
K&R第2版が出版された1988年当時のC言語コードをコンパイル&実行してみました。YouTubeの演習解説動画が非常に役に立ちました。
手順
1. K&R第2版訳本P96のgetch関数ファイルをgetch.cとする。
2. K&R第2版訳本P118のgetint関数ファイルをgetint.cとする。
3. 以下のコードをmain.cとする。
4. main.cをコンパイルし、a.outを実行する。
#include <stdio.h>
#include "getch.c"
#include "getint.c"
main()
{
int ret,c;
if ((ret= getint(&c))==0)
printf("not a valid number\n");
else if (ret > 0)
printf("valid number\n");
else
printf("End of file\n");
printf("return value: %d\n",ret);
return 0;
}
変数への数値のコピーは等式による代入で可能です。
sscanfによる読み込みにおいて3番目の引数は格納先のポインタであり、数値の場合は変数名に&を付け(&horseID)、文字列の場合は変数名(horse_name)そのままとなります。
正確には&horse_name[0]ですが、horse_nameは同じ内容とのことです。また&horse_nameとしてもエラーにはなりません。
<過去コードの該当箇所>
int horseID;
int id;
char horse_name[50];
while(fgets(buf,100, fp ) != NULL ) {
sscanf(buf, "%d, %s",&horseID,horse_name) ; // &horse_nameでも可
if (i != 0){
if (strcmp(horse_name,horse_name_in)==0){
id = horseID; // 数値のコピー
printf("コピー完了 %d %s\n",id,horse_name);
break;
}
}
i ++ ;
}
文字列のコピーにはstrcpyを使います。末尾の\0も含まれるので文字数は1つ以上多くしないとエラーになります。
当初はこれを知らなくて等式でコピーしようとしていました。コンピュータの仕組みを分かっていないと手詰まりになりますね。
<過去コードの該当箇所>
char horseID[10]; // horseIDは9文字だが末尾の\0も入れて10文字とする
char id[10]
if (i != 0){
if (strcmp(horse_name,horse_name_in)==0){
strcpy(id,horseID);
printf("コピー完了 %s\n",id);
}
}
seleniumの動きがもっさりしているため、C/C++のlibcurlというライブラリで高速化を試みました。スクレイピング先のhtmlをまるごとダウンロードして解析し、テキストを抽出してcsvファイルにまとめるという流れです。
その結果、こちらが引くくらい速くなったので全編C言語で書く必要はなくなり、html解析はPythonライブラリのBeautifulSoupで実施しました。スクレイピング先に負荷をかけないようtime.sleepでループの速度を遅くしています。
C言語の方は参考サイトのコードに少しだけ手を入れて完成させました。
今思えばここまでやらなくてもRequests + BeautifulSoupであればPythonだけで高速化できていたかもしれません。時間があれば検証します。
21/07/17追記:C言語にこだわらなければ、以下のコマンドをsubprocessモジュールで走らせてhtmlをダウンロードするのが最も簡単だと思います。
curl “スクレイピング先のurl” > 出力先
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#define EXIT_SUCCESS 0
FILE *fp; // 入力ファイル
FILE *fp2; // 出力ファイル
char fname[] = "horse_url.txt";
char fname2[] = "curl.html";
char buffer[100];
char horse_url[100];
struct Buffer {
char *data;
int data_size;
};
size_t buffer_writer(char *ptr, size_t size, size_t nmemb, void *stream) {
struct Buffer *buf = (struct Buffer *)stream;
int block = size * nmemb;
if (!buf) {
return block;
}
if (!buf->data) {
buf->data = (char *)malloc(block);
}
else {
buf->data = (char *)realloc(buf->data, buf->data_size + block);
}
if (buf->data) {
memcpy(buf->data + buf->data_size, ptr, block);
buf->data_size += block;
}
return block;
}
int main(void) {
CURL *curl;
struct Buffer *buf;
buf = (struct Buffer *)malloc(sizeof(struct Buffer));
buf->data = NULL;
buf->data_size = 0;
fp = fopen(fname, "r");
while(fgets(buffer,100, fp) != NULL ) {
sscanf(buffer,"%s",horse_url);
}
fclose(fp);
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, horse_url);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, buf);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, buffer_writer);
curl_easy_perform(curl);
curl_easy_cleanup(curl);
fp2 = fopen(fname2, "w");
fprintf(fp2, "%s", buf->data);
fclose(fp2);
free(buf->data);
free(buf);
return EXIT_SUCCESS;
}
<C言語コンパイル&実行とBeautifulSoup処理の箇所のみ>
proc = subprocess.run("gcc [ソースコード] -I/usr/local/opt/curl/include -lcurl ; ./a.out ; ECHO 'C言語実行完了'", shell=True, stdout= subprocess.PIPE, stderr = subprocess.PIPE)
print(proc.stdout.decode('UTF-8'))
with open(html,encoding='EUC-JP',errors='ignore') as f:
contents= f.read()
soup = BeautifulSoup(contents, "html.parser")
前回の続きです。
どう考えても検索ヒットした馬名のデータが2行になるのは無駄なので、1行になるよう修正しました。
この修正により処理時間が87秒から80秒に短縮されました。
<修正箇所>
while(fgets(buf,2000, fp ) != NULL ) {
sscanf(buf, " %9s, %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %s",horseID,horse_name,horse_name0,status,gender,hair,birthday,trainer,owner,info,breeder,area,price,prize_money,result,wining_race,relatives ) ;
if (i != 0){
if (strcmp(horse_name,horse_name_in)==0){
fp3 = fopen(fname3, "a");
fprintf(fp3, "%s,%9s\n", horse_name,horseID);
fclose(fp3);
b ++;
break;
}
}
i ++ ;
}
if (b == 0){
fp3 = fopen(fname3, "a");
fprintf(fp3, "%s,100000000\n", horse_name_in);
fclose(fp3);
}
前回の続きです。
C言語実行から作成したデータを処理する流れを説明します。
実行ファイルはsubprocessモジュールで走らせて、処理の終了は実行ファイルの最後に仕込んだ標準出力の受け取りで判断します。
作成されたデータ内容は下図のようになっています。データベースにヒットした馬名は2行になり、ヒットしなかった馬名は1行になります。
あとはpandasなどを使ってデータ加工するのですが、重複行の削除には一工夫必要でした。馬名は複数回登場することがあるのでpandasのduplicatedメソッドは使えず、馬名列要素のリストと1要素分スライドしたリストを比較し重複する所のインデックス番号をリスト化して処理しました。
重たい処理はC言語にさせて、出来上がったラフなデータをPythonで加工する。データベースを扱うに際し私にとって最強の組み合わせになりそうです。
<該当箇所のみ>
# C言語実行ファイル
proc = subprocess.run(C言語実行ファイルのパス, shell=True, stdout= subprocess.PIPE, stderr = subprocess.PIPE)
print(proc.stdout.decode('UTF-8'))
# horseID.csvのデータフレーム化
df = pd.read_csv(horseID_file,names=['馬名2','horseID'],encoding='UTF-8')
# "馬名2"列リストと1要素スライドしたリストを作成(最後の'A'は数合わせ)
list_horseA = df['馬名2'].tolist()
list_horseB = list_horseA[1:] + ['A']
# 重複行のインデックス番号を取得してリスト化
list_num = []
i = 1
for nameA,nameB in zip(list_horseA,list_horseB):
if nameA == nameB:
list_num.append(i)
i = i + 1
# 重複行を削除
df2 = df.drop(df.index[list_num])
前回記事で試みたCモジュールの導入は残念ながらうまくいきませんでしたが、次にPythonコードからターミナルコマンドでC言語実行ファイルを走らせてみたところ、こちらの方は難なく成功しました。
Pythonだけで処理するよりも10倍以上の速さです。
ファイルを介してデータのやり取りをするのであれば、この方法で問題ありません。
C言語のコードを書くのは初心者ゆえかなりしんどいものの、その凄まじい実力を知ってしまったら使わずにはいられないです。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fp; // 誕生年競走馬リスト
int horseID[9]; // 1 horseID
char horse_name[50]; // 2 検索馬名
char horse_name0[50]; // 3 馬名
char status[10]; // 4 稼働
char gender[10]; // 5 性別
char hair[10]; // 6 毛色
char birthday[50]; // 7 生年月日
char trainer[50]; // 8 調教師
char owner[50]; // 9 馬主
char info[100]; // 10 募集情報
char breeder[50]; // 11 生産者
char area[50]; // 12 産地
char price[50]; // 13 セリ取引価格
char prize_money[50]; // 14 獲得賞金
char result[50]; // 15 通算成績
char winning_race[100]; // 16 主な勝鞍
char relatives[100]; // 17 近親馬
char horse_name_in[50]; // 検索馬名
FILE *fp2; // 入力ファイル
FILE *fp3; // 出力ファイル
char buf[2000]; // fgets用
char buf2[200]; // 検索馬名用
int i=0;
int strcmp(const char *s1, const char *s2);
char fname[100];
char fname2[] = "path_horse.txt"; // 誕生年競走馬リストのパスと競走馬名
char fname3[] = "horseID.csv"; // 検索結果
fp2 = fopen(fname2, "r");
while(fgets(buf2,200, fp2) != NULL ) {
sscanf(buf2,"%[^,],%s",fname,horse_name_in);
fp = fopen(fname, "r");
if(fp == NULL) {
printf("%s file not open!\n", fname);
return -1;
}
while(fgets( buf,2000, fp ) != NULL ) {
sscanf(buf, " %9s, %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %s",horseID,horse_name,horse_name0,status,gender,hair,birthday,trainer,owner,info,breeder,area,price,prize_money,result,winning_race,relatives ) ;
if (i != 0){
if (strcmp(horse_name,horse_name_in)==0){
fp3 = fopen(fname3, "a");
fprintf(fp3, "%s,%9s\n", horse_name,horseID);
fclose(fp3);
break;
}
}
i ++ ;
}
fp3 = fopen(fname3, "a");
// ヒットしなかった馬名には仮番号100000000を付ける
fprintf(fp3, "%s,100000000\n", horse_name_in);
fclose(fp3);
fclose(fp);
}
fclose(fp2);
printf("C言語実行しました");
}
競走馬名からIDを検索する部分をC言語で作成し、Python用にモジュール化して走らせてみました。約25000件の処理になります。
速度は6倍になりましたが、検索漏れのエラーが多発して今のところ使えない状態です(エラー率6%)。
丸1日掛けて過去最高レベルで苦労したにも関わらず、残念な結果となりました。
シェルスクリプトのワンライナーでも試してみましたが、処理は正確なものの時間は2倍掛かりました。やはり大量処理には向かないようです。
馬名を都度Cモジュールに渡して検索する方式から一括渡しのバッチ式に変更してさらに検証を進める予定です。
それでも改善しなければ、観念して全編C言語で作成することになるかもしれません。なるべく回避したいところです。
21/7/12追記 バッチ式で検証したところC言語ではOK、モジュール化するとNGでした。モジュール化の方法に問題があるようです。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fp; // horse_listファイル
int horseID[9]; // 1 horseID
char horse_name[50]; // 2 馬名
char horse_name0[50]; // 3 馬名0 (外),(地)等を付加した馬名
char status[10]; // 4 稼働状態
char gender[10]; // 5 性別
char hair[10]; // 6 毛色
char birthday[50]; // 7 生年月日
char trainer[50]; // 8 調教師
char owner[50]; // 9 馬主
char breeder[50]; // 10 生産者
char area[50]; // 11 産地
char price[50]; // 12 セリ取引価格
char prize_money[50]; // 13 獲得賞金
char result[50]; // 14 通算成績
char winning_race[100]; // 15 主な勝鞍
char horse_name_in[50]; // 検索馬名
FILE *fp2; // 入出力用一時ファイル
char buf[600]; // fgets用
char buf2[100]; // 検索馬名用
int ret;
int i=0;
int strcmp(const char *s1, const char *s2);
char fname[100];
char fname2[] = "horseID.txt";
fp2 = fopen(fname2, "r");
while(fgets(buf2,100, fp2) != NULL ) {
sscanf(buf2,"%[^,],%s",fname,horse_name_in);
printf("%s,%s\n",fname,horse_name_in);
fclose(fp2);
}
fp = fopen(fname, "r");
if(fp == NULL) {
printf("%s file not open!\n", fname);
return -1;
}
while( fgets( buf,600, fp ) != NULL ) {
ret = sscanf(buf, " %9s, %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %[^,], %s",horseID,horse_name,horse_name0,status,gender,hair,birthday,trainer,owner,breeder,area,price,prize_money,result,winning_race ) ;
if (i != 0){
if (strcmp(horse_name,horse_name_in)==0){
fp2 = fopen(fname2, "w");
fprintf(fp2, "%9s\n", horseID);
fclose(fp2);
}
}
i ++ ;
}
fclose(fp);
}
[macOS Catalina 10.15.7]
これまで自作モジュールはPythonで書いてきましたが、高速化が必要な場合にC/C++モジュールで対応するためテストしてみました。
1.C言語モジュールの作成
#include <Python.h>
static PyObject*
hello_world (PyObject *self, PyObject *args) {
printf("Hello_world\n");
Py_RETURN_NONE;
}
// メソッドの定義
static PyMethodDef TestMethods[] = {
{"hello_world", (PyCFunction)hello_world, METH_NOARGS, "test1: hello_world"},
{NULL, NULL, 0, NULL}
};
// モジュールの定義
static struct PyModuleDef testmodule = {
PyModuleDef_HEAD_INIT,
"test",
NULL,
-1,
TestMethods
};
// メソッドの初期化
PyMODINIT_FUNC PyInit_test (void) {
return PyModule_Create(&testmodule);
}
2.setup.pyの作成
from distutils.core import setup, Extension
setup(name='test',
version='1.0',
ext_modules=[Extension('test', ['test.c'])]
)
3.C言語モジュールのビルド
python setup.py build_ext -i
4.C言語モジュールの動作チェック
作成されたtest.cpython-39-darwin.soファイルをあらかじめ用意しておいたc_moduleフォルダへコピーし、以下のコードを実行。
c_moduleフォルダをライブラリとして認識させるため、フォルダ内に空の__init__.pyを置くようにする。
そしてc_moduleフォルダまたはシンボリックリンクをこのコードと同じところに置く。
なお今後のためにc_moduleフォルダを作成しましたが、soファイルをソースコードと同じところに置くだけでも1行目”import test”にて実行可能です。
from c_module import test
test.hello_world()
--------------------------------------------------
出力
--------------------------------------------------
Hello_world