データの並べ替えについてお伺いしたい事がございます
yymmdd,code,market,high,low,open,close,value
(Ex:20070801,0101,i,17169,17169,16845,16870,2325910000)
このようなCSV形式のデータファイルが昇順の時系列で約十万行ほどあります、このファ
イルから
code別(コードは約4000種類です)に降順の時系列に並べ替えたファイルを作成したいの
ですが、実用に耐えうるようなプログラムができません。
今まで私が行った手法としては
※1
1 一度元の全データを動的二次元配列に読みこませる
2 code別のファイルに書き込む
結果:PCの動作が物凄く遅くなり途中でOut of Memoryでヒープ領域が確保できませんで
した。
(全データに対してMAX_PATH(260byte)×8×十万行使ったせいかも・・・・)
※2
1 元データをwhile文で逐次読込む
2 目的のコードと一致したら、一時ファイルを新規作成し一致した行を書き込む
3 コード別ファイルを開き、一時ファイルの内容を全て追記し書込む
4 コード別ファイルを削除
5 一時ファイルを4で削除したファイル名にリネーム
結果:夕べ途中で寝てしまったので正確ではありませんが4、5時間かかりましたww
現在、一度元データファイルを固定長のバイナリデータファイルに変換して作成し
データサイズを小さくして、※1の方法で再アプローチしようと考えているのですが
愚かな事でしょうか?
以上2つの方法でアプローチしましたがいづれの方法も目的が成就できません
皆様のお知恵をお借りしたいのですが宜しくお願いいたします。
・Windows XP SP2
・VS2005 MFC
・Memory 1Gbyte
先に言っとくけど実際に試さないとわからないので推測だよ。
エクセルで読んでソートして・・・これじゃプログラムにならないからNGか
だいたいデータ数が増えると悪くて2乗遅くなるからな。
メモリ不足の場合はスラッシングになるからとんでもなく遅くなるよ。
スラッシングになってますか?
数字しか使ってないみたいだからunicodeよりasciiの方が速いかな(推測)
code(約4000種類)別って約4000のファイルを作るってこと?
> 一度元の全データを動的二次元配列に
どんなデータ構造をどんな風にソートするかでかなり遅くなるかもしれない。
二次元配列?
> 260byte)×8×十万行
なんでMAX_PATHや8なの?
200MBなら全部メモリ上で出来なくなくもないような気もするが何でだろう?
まずは遅い原因を知ることと
考えたアプローチがそれを改善するか
もっといいアプローチがあるかどうかかな。
↓マージソートです。
1. メモリに納まる程度、たとえば1000件分読み込んでソートしfile_001 に書く
2. 同様に再度1000件分を読んでソートしfile_002に書く
3. 同様に... ソートされたfile_001~file_nnn ができあがる。
4. file_001 と file_002 をオープンし、双方からひとつづつ読む。
5. 小さい方をfile_xに書き、書いた方のファイルからひとつ読む。
6. 5を繰り返すことでfile_001とfile_002 からソートされた file_xができあがる。
7. file_001 と file_002 を消去し、file_x をfile_001 に名前を変える
8. 4-7をfile_003とfile_004, ... に対して行う。
9. 8の繰り返しによってファイル数は3の半数となる。
4以降の作業を繰り返し、ファイル数が1となったら終了。
# 面倒なので僕ならSQLiteのような簡単なデータベースを使いますけども。
そか、一行50文字として10万行ならわずか5MB、メモリに納まらんわけないなー
C++なら multiset<string>,multimap<string.string>,prioprity_queue<string> 等に
突っ込むだけでソート完了です。
επιστημη さんのおっしゃる通り、メモリに納まると思いますよ。
というわけで私なら、
1. ファイルサイズ分のメモリを確保して一気に読み込む
2. strstrとか使って各行の先頭アドレスをstd::vectorにpush
3. 比較関数とか作ってリストされたアドレスをstd::sort()
4. ソート結果の全行をファイル出力
て感じでしょうかね
ものは試しに長さ52の文字列を10万個ソートしてみた。
#include <set>
#include <algorithm>
#include <string>
int main() {
std::multiset<std::string> table;
std::string data = abcdefghijklmnopqrstuvwxyzABCDEFGHINJKLMNOPQRSTUVWXYZ;
for ( int i = 0; i < 100000; ++i ) {
std::random_shuffle(data.begin(), data.end());
table.insert(data);
}
}
...3秒で終わった ^^;
ファイルの読み書きにどのくらいかかるかわからんが、
ソートにかかるの時間は数秒ってことになりそ。
みなさんお世話になります。
wclrp ( 'o')さん
>code(約4000種類)別って約4000のファイルを作るってこと?
そうです
>どんなデータ構造をどんな風にソートするかでかなり遅くなるかもしれない。
>二次元配列?
一度このように配列に読込み処理してました
char fname[] = C:\\stock_data.data;
long max_col = fctrl.GetMaxLine( fname ); //ファイル長を取
得するクラスライブラリィ
// 動的配列を作成初期化
char** stock_codeList;
long i;
stock_codeList = new char*[ max_col ];
for( i = 0; i < max_col; i++ )
stock_codeList[ i ] = new char[ MAX_PATH ];
for(j = 0; i < max_col; i++)
strcpy_s(stock_codeList[ i ], MAX_PATH, ");
// 一気にヒープへ全データ読み込み
char szBuff[ MAX_PATH ];
strcpy_s(szBuff, MAX_PATH, D:\\200708.txt); // 読込みデータ
fctrl.SetFileName(szBuff);
// 読込みファイル名を設定するクラスライブラリィ
fctrl.SetTextMode();
// テキストモードでファイルを開くライブラリィ
bool type = true;
fctrl.Open(type);
// ファイルを読込むクラスライブラリィ
memset(szBuff, 0, MAX_PATH);
i = 0;
while(fgets(szBuff, MAX_PATH, fp) != NULL)
strcpy_s(stock_codeList[ i ], szBuff);
fctrl.Close();
// ファイル処理終了クラスライブラリィ
>なんでMAX_PATHや8なの?
>200MBなら全部メモリ上で出来なくなくもないような気もするが何でだろう?
そうですね勘違いしていました、一時プログラムを推考している時に一度
一行読込んだデータをセパレータでトークン分割して
typedef struct {
long index;
char yymmdd[MAX_PATH];
char code[MAX_PATH];
char market[MAX_PATH];
char high[MAX_PATH];
char low[MAX_PATH];
char open[MAX_PATH];
char close[MAX_PATH];
char value[MAX_PATH];
} stock_data;
stock_data* STOCKDATA;
この様な動的構造体にほりこんで処理していましたので
先のような勘違いをしてました。
επιστημηさんFUKUさん
やはりvectorの登場ですか、一時MFCのCArrayとかCArrayListの使用も考えましたが
移植性等を思い、自作のクラスライブラリィとネイティブなC++で実装しようと、心がけ
てましたが、やはり今回のようなファイリング処理を行う場合、枯れた(いい意味でww)
boostやvectorのお世話になる方が、後々の保守のこと等も、考えれば懸命な選択かもし
れませんね。
いや、vector,list,set,mapの類は標準C++規格の一部なんだから
これ以上に移植性に優れたもんはないんじゃないかと。
お世話になります、試行錯誤しています、宜しくお願いいたします。
επιστημηさん
確かにそうですね。
FUKUさんへの質問なのですが
>1. ファイルサイズ分のメモリを確保して一気に読み込む
>2. strstrとか使って各行の先頭アドレスをstd::vectorにpush
>3. 比較関数とか作ってリストされたアドレスをstd::sort()
>4. ソート結果の全行をファイル出力
std::multiset<std::string> table;
char szBuff[MAX_PATH];
FILE * fp;
fopen_s(&fp, m_FileName, r);
strcpy_s(szBuff, MAX_PATH, ");
while(fgets(szBuff, MAX_PATH, fp) != NULL) {
table.insert(szBuff);
TRACE(%s, szBuff);
}
fclose( fp );
1. に関しては上記でいいと思うのですが、2.に関してC言語で配列を走査する場合ポイン
ターを使う手法がありますが、その様なイメージでしょうか?
もう少し咀嚼していただけないでしょうか。
2.が分かれば3、4は理解できそうです・・・・・多分
詳細は割愛しますが、SJIS前提でいくと
char* pBuff = new char[dwFileSize +1];
::ReadFile(hFile, pBuff, dwFileSize, &dwResult, NULL);
*(pBuff + dwFileSize) = '\0';
でpBuffにファイルの全内容を読み込み、
vector<char*> lineList;
char* pLine = pBuff;
while (pLine && *pLine) {
lineList.push_back(pLine);
pLine = strstr(pLine, \r\n);
if (pLine)
pLine += 2;
}
みたいな感じでしょうか
ちょっと実験してみました。手順は私が上に書いた通りですが
ソートだけstd::stable_sortに変更しました。(同一キーの相対的順序を保つ為)
実験に使用したCSVは
・SJIS形式10万行(5Mbyte) 改行コードはCRLF
・内容はソイレントグリーンさんのExを元に
・実際にソートされるのは10行程度
以下結果です。
ファイル読み込み時間[16ms]
行アドレスのリスト化[100000行][0ms]
ソート時間[32ms]
結果ファイル出力時間[420ms]
ファイル出力は10万回のWriteFile()でやりましたが
これをメモリ上に出力イメージ作って1回で済ませばもっと速くなるでしょう
実験環境も書いときます
・C2D Mem2G VISTA(32bit) VS2005SP1 MFC未使用
あ、すいません勘違いしてました。出力は1ファイルではなく
code毎に分けるのですか。ならもっと時間掛かりますね^^;
>1 元データをwhile文で逐次読込む
>2 目的のコードと一致したら、一時ファイルを新規作成し一致した行を書き込む
>3 コード別ファイルを開き、一時ファイルの内容を全て追記し書込む
>4 コード別ファイルを削除
>5 一時ファイルを4で削除したファイル名にリネーム
これを、変えてみてはどうでしょう?
1 HANDLE hFile[株価コードの個数];
2 for(int i = 0; i < 株価コードの個数;i++)
hFile[i] = CreateFile(...GENERIC_WRITE);
3 元データから、コードが一致したら、そのコード用に作ったファイルに書き込む。
4 for(...)
CloseHandle(hFile[i]);
時系列で元々ソートされているのなら、プログラム側ではソートせずに、それぞれのコ
ードに対応したファイルに追加していくだけで、時系列ではソートされていることにな
る。
これで如何でしょう?
皆様お世話になります、ソイレントグリーンです。
n_n さんへ
>3 元データから、コードが一致したら、そのコード用に作ったファイルに書き込む。
これですと、作成されるデータの日付が元データと同じ昇順になってしまい目的が成就で
きないと思うのですが?
例えば、既にコード毎にソート済みのデータの並びがこの様な場合
20070801,0101,i,18000,16000,15000,14000,2000000000
20070802,0101,i,18100,16100,15100,14100,2100000000
20070803,0101,i,18200,16200,15200,14200,2200000000
日付を降順にファイル出力を行いたいのですが
20070803,0101,i,18200,16200,15200,14200,2200000000
20070802,0101,i,18100,16100,15100,14100,2100000000
20070801,0101,i,18000,16000,15000,14000,2000000000
FUKUさんへ
現在ご教示して頂いたアルゴリズムで実装しているのですが、
ソートの部分で躓いています。
ファイル出力の前に、コード毎にデータをまとめ、降順の日付に並べ替える作業が必要な
のですがそのためには二段階でソートを行わなければ、ならないと思うのですが
1. yymmdd(日付)を主キーに降順で全体を並べ替える
2. 1.で並べ替えたデータをcode(コード)毎に並べ替える
(1、2の手順は逆も可)
具体的な実装をどの様に行ったらよいのでしょうか、ご教示願えないでしょうか。
char m_FileName[] = 読込みデータ;
HANDLE hFile=::CreateFile(m_FileName,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,NULL);
if ( hFile==NULL )
return FALSE;
DWORD dwFileSize = GetFileSize(hFile , NULL);
char* pBuff = new char[dwFileSize + 1 ];
static DWORD dwResult;
::ReadFile(hFile, pBuff, dwFileSize, &dwResult, NULL);
*(pBuff + dwFileSize) = '\0';
vector<char*> lineList;
char* pLine = pBuff;
while (pLine && *pLine) {
//行のアドレスを配列に詰め込む
lineList.push_back(pLine);
//pLineにインスタンスを代入
pLine = strstr(pLine, \r\n);
//TRACE(%s\n, pLine);
if (pLine) {
pLine += 2;
}
}
//ソートを行う、これ以降の実装が分かりません?
stable_sort(lineList.begin(), lineList.end());
//イテレータのインスタンス化
vector<char*>::iterator it = lineList.begin();
while( it != lineList.end() )
{
//TRACE(%d\n, *it);
++it;
}
delete []pBuff;