Php array_multisort对2个相同的多维数组进行不同排序

Php array_multisort对2个相同的多维数组进行不同排序,php,Php,我试图通过将两个csv文件导入多维数组并使用array_diff函数找出差异来比较php中的两个csv文件 我使用的方法是 1) 获取预期csv的每条记录并转储到arr1中 2) 获取实际csv的每条记录并转储到arr2中 3) 使用数组_multisort对数组1进行排序 4) 使用array\u multisort对array2进行排序 5) 使用数组_diff函数比较每条记录(例如arr1[0][1]与arr2[0][1]) 我的目标是使用php脚本在尽可能短的时间内比较文件。我发现上面的

我试图通过将两个csv文件导入多维数组并使用array_diff函数找出差异来比较php中的两个csv文件

我使用的方法是

1) 获取预期csv的每条记录并转储到arr1中

2) 获取实际csv的每条记录并转储到arr2中

3) 使用数组_multisort对数组1进行排序

4) 使用array\u multisort对array2进行排序

5) 使用数组_diff函数比较每条记录(例如arr1[0][1]与arr2[0][1])

我的目标是使用php脚本在尽可能短的时间内比较文件。我发现上面的方法是最短的(最初尝试将th csv内容转储到MySQL中,并使用db查询进行比较,但由于一些未知的原因,这些查询的运行速度非常慢,以至于超时后会导致Apache服务器崩溃)

我的csv文件大小高达300mb,但通常是70k记录,20列,10mb大小

我正在粘贴我所做工作的代码(w.r.t上述步骤)

更新:两个csv文件可能包含不同的日期格式,并且每个文件可能以不同的格式表示数字,如1.csv可以将2013年1月12日和0.01作为第一行……2.csv可以将2013年12月1日和.01日作为第一行
因此,我认为哈希不起作用

确定这两个文件是不同的吗?首先,我会使用md5_文件并比较这两个文件的md5哈希值,以检查它们是否有任何不同

如果它们不同,我会做如下事情:

$csv_1_path = 'file_1.csv';
$csv_2_path = 'file_2.csv';
$fh_csv_1 = fopen($csv_1_path, 'r');
$fh_csv_2 = fopen($csv_2_path, 'r');
$md5_1 = array();
$md5_2 = array();
while( !feof($fh_csv_1) ) {
  $md5_1[] = md5(fgets($fh_csv_1));
}

while( !feof($fh_csv_1) ) {
  $md5_2[] = md5(fgets($fh_csv_2));
}

$common_records = array_intersect($md5_1, $md5_2);

$records_diff_count = 0;
foreach($md5_1 as $row_index => $md5_rec_1) {
  if ( !in_array($md5_rec_1, $common_records) ) {
    print "Record in file $csv_1_path, row $row_index has no match.\n";
    $records_diff_count++;
  }
}

foreach($md5_2 as $row_index => $md5_rec_2) {
  if ( !in_array($md5_rec_2, $common_records) ) {
    print "Record in file $csv_2_path, row $row_index has no match.\n";
    $records_diff_count++;
  }
}

找到每个文件的行索引后,可以对文件之间的差异进行更深入的分析。

快速观察

$csvA = "a.log";
$csvB = "b.log";

echo PHP_EOL;

$hashA = readCSVFile($csvA);
$hashB = readCSVFile($csvB);

// Lines in A not in B
$hash = array_diff($hashA, $hashB);
if (($fp = fopen($csvA, "r")) !== FALSE) {
    foreach ( $hash as $p => $v ) {
        fseek($fp, $p);
        echo implode("|",array_map("trim", fgetcsv($fp, 2024, "|"))), PHP_EOL;
    }
    fclose($fp);
}
236|INPQR|31-AUG-12|200|INR|6653|AAAAAA,PPPPP|0|0|0|0|0
225|INPZQ|31-AUG-12|200|USD|6655|AAAAAA,PPPPP|0|0|0|0|0
225|INPZQ|31-AUG-12|200|INR|6654|AAAAAA,PPPPP|0|0|0|0|0
  • 您不需要排序来获得差异
  • 我不确定如何将
    300MB
    有效加载到
    PHP应用程序中
    ,但显然您没有内存问题,因为我建议您改用
    SQL
    Map Reduce
  • 您的
    CSV
    有点滑稽,很难断定
    |
    空格
    是分隔符。
    • 因为你有
      20列
      wow。。。对diff使用哈希将比整个20列更好。。然后,您可以确定检索内容的位置
代码的简单版本

$csvA = "a.log";
$csvB = "b.log";

echo PHP_EOL;

$hashA = readCSVFile($csvA);
$hashB = readCSVFile($csvB);

// Lines in A not in B
$hash = array_diff($hashA, $hashB);
if (($fp = fopen($csvA, "r")) !== FALSE) {
    foreach ( $hash as $p => $v ) {
        fseek($fp, $p);
        echo implode("|",array_map("trim", fgetcsv($fp, 2024, "|"))), PHP_EOL;
    }
    fclose($fp);
}
236|INPQR|31-AUG-12|200|INR|6653|AAAAAA,PPPPP|0|0|0|0|0
225|INPZQ|31-AUG-12|200|USD|6655|AAAAAA,PPPPP|0|0|0|0|0
225|INPZQ|31-AUG-12|200|INR|6654|AAAAAA,PPPPP|0|0|0|0|0
输出

$csvA = "a.log";
$csvB = "b.log";

echo PHP_EOL;

$hashA = readCSVFile($csvA);
$hashB = readCSVFile($csvB);

// Lines in A not in B
$hash = array_diff($hashA, $hashB);
if (($fp = fopen($csvA, "r")) !== FALSE) {
    foreach ( $hash as $p => $v ) {
        fseek($fp, $p);
        echo implode("|",array_map("trim", fgetcsv($fp, 2024, "|"))), PHP_EOL;
    }
    fclose($fp);
}
236|INPQR|31-AUG-12|200|INR|6653|AAAAAA,PPPPP|0|0|0|0|0
225|INPZQ|31-AUG-12|200|USD|6655|AAAAAA,PPPPP|0|0|0|0|0
225|INPZQ|31-AUG-12|200|INR|6654|AAAAAA,PPPPP|0|0|0|0|0
使用的功能

function readCSVFile($file, $size = 2024, $delimiter = "|") {
    $hash = array();
    if (($fp = fopen($file, "r")) !== FALSE) {
        $t = ftell($fp);
        while ( ($data = fgetcsv($fp, $size, $delimiter)) !== FALSE ) {
            $hash[$t] = sha1(implode(array_map("trim", $data)));
            $t = ftell($fp);
        }
        fclose($fp);
    }
    return $hash;
}

您可以使用下面的行对数据进行正确排序

<?php
function readCSV($fileName, $delimiter, $exclude_cols = array()) {
    $data = array();
    $fh = fopen($fileName, 'r');
    while ($line = fgetcsv($fh, 0, $delimiter)) {
        if (count($line) == 1 && $line[0] == '') {
            continue;
        }

        for ($i = 0; $i < count($line); $i++) {
            $line[$i] = in_array($i, $exclude_cols, true) ? 'NTBT' : trim($line[$i]);
        }
        $data[] = $line;
    }
    fclose($fh);
    return $data;
}

function sort2dArray($data) {
    $tmp = array();
    $lineCount = count($data);
    foreach ($data as $lineNum => $lineData) {
        foreach ($lineData as $column => $value) {
            $tmp[$column][$lineNum] = $value;
        }
    }

    $multiSortArgs = array();
    foreach ($tmp as $column => &$columnData) {
        array_push($multiSortArgs, &$columnData, SORT_ASC);
    }
    $multiSortArgs[] = &$data;
    call_user_func_array('array_multisort', $multiSortArgs);
    return $data;
}

// ========= Reading and sorting
// The expected data
$data_Exp = readCSV($fileExp, $_POST['dl1']);
$rarr1 = sort2dArray($data_Exp);

// The actual data
$data_Act = readCSV($fileAct, $_POST['dl2']);
$rarr2 = sort2dArray($data_Act);

有许多不同的方法来比较两个CSV文件。我使用了一种方法来检查两个文件中的不同行。我考虑到您希望从行中删除某些列

我没有使用排序,因为我检查一行是否在另一个文件中,而不是它是否在同一位置。原因很简单:如果有一行不匹配,并在文件开头排序,则该行之后的所有行都将不同

例如:

file1:  file2:
1|a     1|a
2|b     2|b
3|c     3|c
4|d     4|d
5|e     1|e

After sorting

file1:  file2:
1|a     1|a
2|b     1|e
3|c     2|b
4|d     3|c
5|e     4|d

Now the rows 2, 3, 4, and 5 are all marked as different, because they do not match if you check per line. But in fact only 1 row is different.
在下面的代码中,您将看到关于我为什么做某事的注释。我还在几个大型CSV文件(约45mb和100.000行)上测试了代码,并在每次检查不到10秒的时间内得到了不同行的数量

<?php
set_time_limit(0);

//create a function to create the CSV arrays.
//If you create the code twice like you did, you are bound to make a mistake or change something in one place and not the other.
//Obviously that could lead to sorting two equal files differently.
function CsvToArray($file) 
{
  $exclude_cols = array(2); //you didnt provide it, so for testig i remove the date col because its always the same

  //load file contents into variable and trim it
  $data = trim(implode('', file($file)));

  //strip \r new line to make sure only \n is used
  $data = str_replace("\r", "", $data);
  //strip all spaces from |
  $data = preg_replace('/\s\s+\|/', '|', $data);
  $data = preg_replace('/\|\s\s+/', '|', $data);
  //strip all spaces from each line
  $data = preg_replace('/\s\s+\n/', "\n", $data);
  $data = preg_replace('/\n\s\s+/', "\n", $data);

  //each line to seperate row
  $data = explode("\n", $data);

  //each col to seperate record
  //This is only needed for comparisment if you want to remove certain cols
  //if thats not needed, you can skip this part    
  foreach($data as $k=>$v)
    $data[$k] = explode('|', $v);

  //get the header. Its always the first row
  //array_shift will return the first element and remove it from the dataset
  $header = array_shift($data);

  //turn the array around, by making the row the key and count howmany times it shows
  $ar = array();
  foreach ($data as $row) {
    //remove unwanted cols
    //if you dont want to remove certain cols, skip this and the implode part and use $ar[$row]++
    foreach($exclude_cols as $c)
      $row[$c] = '';
    //implode the remaining
    $key = implode('', $row);

    //you can use str_to_lower($key) for case insensive matching
    $ar[$key]++;    
  }

  return $ar;
}

function CompareTwoCsv($file1, $file2)
{
  $start = microtime(true);

  $ar1 = CsvToArray($file1);
  $ar2 = CsvToArray($file2);

  //check for differences.
  $diff = 0;
  foreach($ar1 as $k=>$v) {
    //the second array doesnt contain the key (is row) so there is a difference
    if (!array_key_exists($k, $ar2)) {
      $diff+=$v; //all rows that are in the first array are different
      continue;
    }
    $c2 = $ar2[$k];

    if ($v == $c2) //row is in both file an equal number of times
      continue;

    $diff += max($v, $c2) - min($v, $c2); //add the number of different rows
  }

  $ar1_count = count($ar1);
  $ar2_count = count($ar2);

  //if ar2 has more records. Every row that is more, is different.
  if ($ar2_count>$ar1_count)
    $diff += $ar2_count - $ar1_count;

  $end = microtime(true);
  $difftime = $end - $start;

  //debug output
  echo "We found ".$diff." differences in the files. it took ".$difftime." seconds<hr>";
}

//test and test2 are two files with ~100.000 rows based on the data you supplied.
//They have many equal rows in the files, so the array returned from CsvToArray is small
CompareTwoCsv("test.txt", "test.txt");
//We found 0 differences in the files. it took 5.6848769187927 seconds

CompareTwoCsv("test.txt", "test2.txt");
//We found 17855 differences in the files. it took 6.6002569198608 seconds

CompareTwoCsv("test2.txt", "test.txt");
//We found 17855 differences in the files. it took 7.5223989486694 seconds


//randomly generated files with 100.000 rows. Very little duplicate data;

CompareTwoCsv("largescv1.txt", "largescv2.txt");
//We found 98250 differences in the files. it took 5.4302139282227 seconds

?>


结果:

你就不能比较一下
md5
hash之类的东西吗?如果行的顺序可能不同,那么是什么标识行?这些列可以不同吗?这些列有固定的顺序。行顺序可能会上下波动。这就是为什么我在所有列上使用排序。然而,可能有这样一个例子,行顺序不匹配,但是那些会丢失错误记录(考虑到在对实际结果和预期结果进行排序之后,行顺序应该匹配!)请建议一个备选方案,如果您有想法的话……您考虑过修剪列值吗?如果您检查代码,我会这样做……有修剪(科瓦尔)你能放一些两个csv文件的样本数据吗?是的,这两个文件可能非常不同。它可能有一些记录字段不匹配,甚至可能在实际和预期之间有额外/更少的行。任何事情都是可能的。这就是我在比较之前对两个文件使用排序的原因。你的上述代码在没有排序?不,我认为不需要排序。您需要隔离每个文件唯一的记录,然后执行任何单独的测试。我猜在删除一些常见记录后,这将快得多。如果可以以“模糊”方式将两行视为相等,则会出现问题,例如,这意味着有如下规则“如果记录_2中缺少列_1,则可以”或“如果列_3的值在范围X内,则可以接受”".是的,我计划包括上述模糊场景,因为有时我可能需要跳过某些差异…任何解决方法的建议?视情况而定…你能举个例子吗?如果你容忍两条记录之间的差异,你需要分别检查每条记录。但是,如果字段值受到限制,你可以简单地进行调整准确地说,我有可能在实际或预期中丢失行,我想突出显示o/p等实例。这里的目的是比较实际与预期,突出显示差异!使用上述代码会给我带来一个错误-“已弃用:已弃用按引用传递调用时间"我注意到,如果我对文件1和文件2使用两个不同的分隔符,它无法正确排序。当它在两个文件之间遇到3.10E+06和31E5的数据时,它会出现排序错误。虽然它们表示相同的分隔符为“|”。我知道这里的每个字段都充满了大量空格,但这是我收到的方式但是我确实需要修剪每个字段。另外,我想知道你的SQL或Map reduce方法。我尝试了o