誕生日にもらった意外なもの

C++初心者です。ご指導よろしくお願いします。

C++で特定の行の値を読み込むプログラムを作っています。

a.txtとb.txtが入力ファイルで、c.txtが出力ファイルです。


a.txtには
237891
193203
1355876



(以下1~5000000の数値がランダムに15000行分)


b.txtには
0.333333
0.333333
0.397396



(以下0.333333~0.822222までの数値がランダムに5000000行分)

が書いてあって、


c.txtに
a.txtの1行目の数値の行に対応するb.txtの値
a.txtの2~
a.txtの3~



(以下15000行分)

を出力するプログラムを作りたいと思っています。

以下のように、プログラムを書きましたが、a.txtが10行、b.txtが20行程度の時は問題なく動くのですが、行数が多くなると急に動かなくなります。
charのところを変えたり、offsetのところを変えたりしたのですが、最初の1行を読み込んだところで止まってしまいます。
(buffの値は=237891
no2の値は=237891まで)

どのようにすれば動くようになるでしょうか?
ご指導よろしくお願いします。


#include <stdio.h>
#include <iostream>
#include <fstream>
#include <cstdlib>
#include <cstring>
using namespace std;

int main(void)
{
FILE *fp,*fp2,*fp3;
char buff[256],buff2[256];
long int offset[100],offset2[100];
long int max,max2;
long int no=0;
long int no2=0;

for(no=1; no<=15000; no++){
fp = fopen("input/a.txt","r");
if(fp == NULL){
cout << "入力ファイルをオープンできません\n";
}

for ( max = 0 ; !feof(fp) ; max++ ){
if ( max >= 100 ){
break;
}
offset[ max ] = ftell( fp );
fgets( buff, sizeof(buff), fp );
}

fseek( fp, offset[no - 1], SEEK_SET );
fgets( buff, sizeof(buff), fp );

cout << "buffの値は=" << buff << "\n";

no2 = atoi(buff);

cout << "no2の値は=" << no2 << "\n";



fp2 = fopen("input/b.txt","r");

if(fp2 == NULL){
cout << "入力ファイルをオープンできません\n";
}

for ( max2 = 0 ; !feof(fp2) ; max2++ ){
if ( max2 >= 100 ){
break;
}
offset2[ max2 ] = ftell( fp2 );
fgets( buff2, sizeof(buff2), fp2 );
}

fseek( fp2, offset2[no2 - 1], SEEK_SET );
fgets( buff2, sizeof(buff2), fp2 );

cout << "buff2の値は=" << buff2 << "\n";

fp3 = fopen("input/c.txt","a");

if(fp2 == NULL){
cout << "入力ファイルをオープンできません\n";
}

fprintf(fp3, buff2);

strcpy(buff,"0");
strcpy(buff2,"0");
no2=0;

cout << "buff2は初期化されて=" << buff2 << "\n";

fclose(fp);
fclose(fp2);
fclose(fp3);
}
}

A 回答 (7件)

すみません。

ftellを見落としてました。


> long int offset[100],offset2[100];
ローカル変数(この変数はmain関数内だけで有効なローカル変数です)でこのように宣言されたものは、自動変数といって、スタック領域と呼ばれるメモリ空間に確保されます。
この領域はそんなに大きくありません。Visual C++の標準で1MBだそうです。
100万バイトに500万×4(longのバイト数)は入るはずもありません。

大きな配列を使う場合は、次のような方法があります。
1) ヒープ領域を使う
Cならmalloc/calloc, C++ならnew演算子を使います。使い終ったら free / delete[]で解放します。
解説書ではポインタのところに詳しく書いてあるはずです。

2) 静的変数を使う。
静的変数についての詳細は、解説書などを参考に。
static long int offset[5000000],offset2[5000000];
とすると、最初から大きなメモリが使えます。
静的変数にはいろいろ制約があるのですが、今回のケースなら問題ないです。

3) C++限定: 配列と同等の機能を持つクラスを利用する(std::vector等)


それから
ディスクアクセスは、非常に遅い処理です。
メモリがナノ(10^-9)秒オーダーなのに対し、ディスクのシークタイムはせいぜいミリ秒(10^-3)オーダーです。
速度を求めるなら、アクセスは極力減らすべきです。

このプログラムだと
aを1行読み込み→bを全部読み込み(オフセットを取得)→bを1行読み込み→cに書き込み
となっています。bの読み込みだけでは(500万行)×1万5千(aの行数)×9文字(1行あたりの文字数)=675GBです。
700GBくらいのハードディスクを丸々コピーするようなものです。

すでにb.txtの読み出す場所は判っている( no2 行目)のですから、そこまで読めばオフセットは判ります。
さらに、この方法だと、forが終った時点でbuff2に所望の行が入っているわけですから、offset2もfseekも必要ありません。
for ( max2 = 0 ; (!feof(fp2)) && (max2 < no2) ; max2++ ){
fgets( buff2, sizeof(buff2), fp2 );
}
cout << "buff2の値は=" << buff2 << "\n";

さらに、a.txtのfopen/fcloseをfor(max=...の外に出せば、 a.txt用のoffsetも不要です。

fp = fopen("input/a.txt","r");
for(no=1; no<=15000; no++){
fgets( buff, sizeof(buff), fp );
no2 = atoi(buff);
...
/*削除: fclose(fp); */
}
fclose(fp);
}

以上が省メモリ版です。オリジナルに比べて平均で半分くらいの時間で実行できるはずです。(それでも300GBのコピーくらいの時間ですが)
    • good
    • 0

ああそうだ, 「fseek の使い方」そのものはこれであってますよ>#4. ちゃんと「ftell で得られる情報」を使ってますから.


C++ でいくなら, たとえば
#include <fstream>
#include <vector>
#include <string>
#include <iterator>
#include <algorithm>
using namespace std;

int main()
{
vector<string> data;
{
ifstream fb("input/b.txt");
string buf;
while (getline(fb, buf))
data.push_bacK(buf);
}

transform(istream_iterator<int>(ifstream("input/a.txt")), istream_iterator<int>(), ostream_iterator<string>(ofstream("input/c.txt", ios_base::app), "\n"), [&data](int x) { return data[x-1]; });
return 0;
}
のように書けるはず... なんだけど, なぜか GCC 4.5.1 ではコンパイルに失敗する. *_iterator の引数に直接 *stream を入れられない. う~む.
もちろん data を vector<double> にすれば, もっと短くなる.
    • good
    • 0

まあ確かに Perl の方が簡単かも>#4.


perl -MFileHandle "FileHandle->new('input/c.txt','a')->print(('', FileHandle->new('input/b.txt', 'r')->getlines)[FileHandle->new('input/a.txt', 'r')->getlines]);"
のワンライナーですしね... って, さすがにこれはやりすぎ?
    • good
    • 0

気になる点


・a.txtの数字は、行数ですか?バイト数ですか?
fseekに指定するオフセットは「バイト数」です。
「行数」ではありません。

・a.txtとb.txtの関係は?
双方とも完全に乱数ならば、b.txtの先頭(あるいは、任意のところから)aの行数分だけ取りだしても同じだと思うのですが。
a→bの計算式があるなら、bを読まずに計算するという方法もありますし。

・C++でやる理由は?
この質問にあるだけなら、デバッグと実行時間まで含めてPerlでも使ったほうが早く終わりそうです。
    • good
    • 0

メモリ不足かもしれんしそうではないかもしれん. 単に「動かない」としか書いてくれないので, どちらであるかを判断することは全く不可能だ.



しかし, よく見るととてつもなく無駄なプログラムだなぁ. このプログラムからはちょっと離れて, 動作をもっと練った方がいいと思うよ. ところで, b.txt にあるのは小数だと思っていい?

この回答への補足

はい。b.txtは小数です。

補足日時:2010/09/22 15:33
    • good
    • 0

現状だと


fseek( fp2, offset2[no2 - 1], SEEK_SET );
で no2 が 100 を超えてたらアウトだってことに気づいてない?

でまあ普通は (かつメモリに余裕があれば) #1 の方法が「何も考えなくていい」ので簡単.
逆に a.txt から全部読み込んで, ソートしてから b.txt を読み込むという方針もあります. 今の条件設定ならこっちの方がメモリは少なくてすむ. けどめんどくさいので, よほどメモリが苦しいとき限定で.

この回答への補足

FILE *fp,*fp2,*fp3;
char buff[256],buff2[256];
long int offset[5000000],offset2[5000000];
long int max,max2;
long int no=0;
long int no2=0;

for(no=1; no<=15000; no++){
fp = fopen("input/a.txt","r");
if(fp == NULL){
cout << "入力ファイルをオープンできません\n";
}

for ( max = 0 ; !feof(fp) ; max++ ){
if ( max >= 5000000 ){
break;
}

にすると動かなくなります。
これは、メモリ不足ということなのでしょうか?

補足日時:2010/09/22 15:00
    • good
    • 0

b.txtを配列に全て読み込んでからa.txtの値を配列の添え字とすればいいのでは。


b.txtは5,000,000件からさらに増加するのですか?

この回答への補足

いえ、b.txtは既知で500万行で、増えません。

b.txtを配列で読み込んでから、a.txtを読み込む方が自然なのでしょうか?

文章がわかりづらかったらすみません。

補足日時:2010/09/22 15:02
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!