我正在寻找一种算法策略。我有一个162列和55000行的csv文件。 我想订购一个日期的数据(在第3列)。
首先,我尝试直接将所有内容放入数组,但内存爆炸。
所以我决定: 1 /将前三列放在一个数组中。 2 /使用usort排序此数组 3 /读取csv文件以恢复其他列 4 /在新的csv文件中添加整行 5 /用读取的csv文件上的空字符串替换该行
//First read of the file
while(($data = fgetcsv($handle, 0,';')) !== false)
{
$tabLigne[$columnNames[0]] = $data[0];
$tabLigne[$columnNames[1]] = $data[1];
$tabLigne[$columnNames[2]] = $data[2];
$dateCreation = DateTime::createFromFormat('d/m/Y', $tabLigne['Date de Création']);
if($dateCreation !== false)
{
$tableauDossiers[$row] = $tabLigne;
}
$row++;
unset($data);
unset($tabLigne);
}
//Order the array by date
usort(
$tableauDossiers,
function($x, $y) {
$date1 = DateTime::createFromFormat('d/m/Y', $x['Date de Création']);
$date2 = DateTime::createFromFormat('d/m/Y', $y['Date de Création']);
return $date1->format('U')> $date2->format('U');
}
);
fclose($handle);
copy(PATH_CSV.'original_file.csv', PATH_CSV.'copy_of_file.csv');
for ($row = 3; $row <= count($tableauDossiers); $row++)
{
$handle = fopen(PATH_CSV.'copy_of_file.csv', 'c+');
$tabHandle = file(PATH_CSV.'copy_of_file.csv');
fgetcsv($handle);
fgetcsv($handle);
$rowHandle = 2;
while(($data = fgetcsv($handle, 0,';')) !== false)
{
if($tableauDossiers[$row]['Caisse Locale Déléguée'] == $data[0]
&& $tableauDossiers[$row]['Date de Création'] == $data[1]
&& $tableauDossiers[$row]['Numéro RCT'] == $data[2])
{
fputcsv($fichierSortieDossier, $data,';');
$tabHandle[$rowHandle]=str_replace("\n",'', $tabHandle[$rowHandle]);
file_put_contents(PATH_CSV.'copy_of_file.csv', $tabHandle);
unset($tabHandle);
break;
}
$rowHandle++;
unset($data);
unset($tabLigne);
}
fclose($handle);
unset($handle);
}
该算法确实执行时间太长,但是可以正常工作
有什么想法要改进吗?
谢谢
答案 0 :(得分:1)
您有大量数据要处理,因此您需要做一些 来对其进行优化。
您可以增加内存,但这只会推迟错误,当文件更大时,它将崩溃(或使waaaayy太慢)。
第一个选择是尝试最小化数据量。从文件中删除所有不相关的列。无论采用哪种解决方案,较小的数据集总是更快。
我建议您将其放入数据库并对其应用需求,然后使用该结果创建一个新文件。建立了一个数据库来管理大型数据集,因此将花费更少的时间。
从PHP接收大量数据并将其写入文件仍然会很慢,但是可以管理。另一种策略可能是使用命令行,使用.sh
文件。如果您具有基本的终端/ ssh技能,则具有基本的.sh
编写功能。在该文件中,您可以使用mysqldump to export as csv like this。 Mysqldump将显着提高速度,但是当您习惯使用PHP时,操作起来会有些棘手。
要改善当前代码,请执行以下操作:
-第一个结尾处的unset
不会做任何有用的事情。它们几乎不会存储数据,并且无论何时开始发出下一个提示时,它们仍然会重置。
-使用纪元值代替所有事情的DateTime()
,这更容易使用,但速度较慢。我不知道它现在以什么格式出现,但是如果您使用纪元秒(例如time()
的结果),则会有两个数字。您的usort()
将大大改善,因为它不再需要使用繁重的DateTime
类,而只是简单的数字比较。
这全部假设您需要做多次。如果没有,只需在Excel或Numbers中将其打开,然后使用该排序并将其另存为副本即可。
答案 1 :(得分:1)
假设您仅限于使用PHP,并且不能按照注释中的建议使用数据库来实现它,那么下一个最佳选择是使用external sorting算法。
可以非常高效地完成排序文件的合并:只需在任何给定时间在内存中保存每个文件的第一行。时间戳最小的第一行应转到生成的文件。
对于真正的大文件,您可以层叠合并,即:如果有10,000个文件,则可以先合并100个文件的组,然后合并结果的100个文件。
为了便于阅读,我使用逗号来分隔值而不是换行符。
未排序的文件(想象它太大而无法容纳到内存中):
1, 6, 2, 4, 5, 3
将文件拆分成足够小以适合内存的方式:
1, 6, 2
4, 5, 3
分别对它们进行排序:
1, 2, 6
3, 4, 5
现在合并:
答案 2 :(得分:0)
我会将数据加载到数据库中,然后再担心底层算法。
如果这是一次性的问题,我建议不要自动执行,而是使用电子表格。
答案 3 :(得分:0)
我仅在一个小文件上尝试过此操作,但是原理与您读取文件,存储日期然后对其进行排序的想法非常相似。然后读取原始文件并写出排序的数据。
在此版本中,加载仅读取日期并创建一个数组,该数组保存日期和行首在文件中的位置(每次读取后使用ftell()
来获取文件指针)
然后对这个数组进行排序(因为日期首先只是使用常规排序)。
然后,它遍历已排序的数组,并且对于每个条目,它使用fseek()
在文件中定位记录,并读取行(使用fgets()
)并将此行写入输出文件。
$file = "a.csv";
$out = "sorted.csv";
$handle = fopen($file, "r");
$tabligne = [];
$start = 0;
while ( $data = fgetcsv($handle) ) {
$tabligne[] = ['date' => DateTime::createFromFormat('d/m/Y', $data[2]),
'start' => $start ];
$start = ftell($handle);
}
sort($tabligne);
$outHandle = fopen( $out, "w" );
foreach ( $tabligne as $entry ) {
fseek($handle, $entry['start']);
$copy = fgets($handle);
fwrite($outHandle, $copy);
}
fclose($outHandle);
fclose($handle);