以最快的方式将2d数组转换为3d数组,并按特定值分组


PHP - Fastest way to convert a 2d array into a 3d array that is grouped by a specific value

我想转换这个二维数组的记录:

[records] => Array
(
  [0] => Array
  (
    [0] => Pears
    [1] => Green
    [2] => Box
    [3] => 20
  )
  [1] => Array
  (
    [0] => Pears
    [1] => Yellow
    [2] => Packet
    [3] => 4
  )
  [2] => Array
  (
    [0] => Peaches
    [1] => Orange
    [2] => Packet
    [3] => 4
  )
  [3] => Array
  (
    [0] => Apples
    [1] => Red
    [2] => Box
    [3] => 20
  )
)

进入这个三维数组,其中每个数组键按原始数组中的某个值分组:

[converted_records] => Array
(
  [Pears] => Array
  (
    [0] => Array
    (
      [0] => Green
      [1] => Box
      [2] => 20
    )
    [1] => Array
    (
      [0] => Yellow
      [1] => Packet
      [2] => 4
    )
  )
  [Peaches] => Array
  (
    [0] => Array
    (
      [0] => Orange
      [1] => Packet
      [2] => 4
    )
  )
  [Apples] => Array
  (
    [0] => Array
    (
      [0] => Red
      [1] => Box
      [2] => 20
    )
  )
)

我可以这样做:

$array = // Sample data like the first array above
$storage = array();
$cnt = 0;
foreach ($array as $key=>$values) {
  $storage[$values[0]][$cnt] = array (
    0 => $values[1],
    1 => $values[2],
    2 => $values[3]
  );
  $cnt ++;
}

我想知道是否有更优的方法来做到这一点。我不知道PHP中的任何函数都能做到这一点,所以我只能假设这基本上是如何完成的。

问题是,这要重复很多次每一毫秒都很重要所以我真的很想知道完成这个任务的最好方法是什么?

编辑

记录数组是通过解析.CSV文件创建的,如下所示:

$records = array_map('str_getcsv', file('file.csv'));

编辑# 2

我对一组10个结果(每个5k条记录)进行了一个简单的基准测试,以获得平均运行时间为0.645478秒。当然,在此之前还有一些其他的事情要做,所以这不是实际性能的真正指示,但这是与其他方法进行比较的一个很好的指示。

编辑# 3

我用大约20倍的记录做了一个测试。我的平均作息时间是14.91971。

@num8er下面的答案在更新答案之前有$records[$key][] = array_shift($data);,就像现在一样。

当我尝试使用更大的结果集进行测试时,它耗尽了内存,因为它为每个记录生成了一个错误。

话虽如此,一旦我执行了$records[$key][] = $data;,该例程平均完成时间为18.03699秒,gc_collect_cycles()被注释掉。

我已经得出结论,虽然@num8ers方法对于较小的文件更快,但对于较大的文件,我的方法更快。

如果你只是在寻找一些干净的代码:

$array   = array_map('str_getcsv', file('file.csv'));
$storage = array();
foreach ($array as $values) {
    $key             = array_shift($values);
    $storage[$key][] = $values;
}

除非你有成千上万的数组条目,否则速度也不应该是一个问题。

使用file()读取大文件到内存(读取文件时的第一次迭代)
然后使用array_map迭代行(每一行文件被读入数组后的第二次迭代)
对数组执行foreach(第三次迭代)
当你追求性能时,这是个坏主意。

迭代3次。那么100,000条记录呢?它将迭代30万次?
最高效的方法是在读取文件时执行此操作。只有1次迭代-读取行(100K记录== 100K迭代):

ini_set('memory_limit', '1024M');
set_time_limit(0);
$file = 'file.csv';
$file = fopen($file, 'r');
$records = array();
while($data = fgetcsv($file)) {
  $key = $data[0];
  if(!isset($records[$key])) {
    $records[$key] = array();
  }
  $records[$key][] = array(0 => $data[1],
                           1 => $data[2],
                           2 => $data[3]);
  gc_collect_cycles();
}
fclose($file);


下面是parent -> children对大文件的处理:

<?php
ini_set('memory_limit', '1024M');
set_time_limit(0);
function child_main($file)
{
    $my_pid = getmypid();
    print "Starting child pid: $my_pid'n";
    /**
     * OUR ROUTINE
     */
    $file = fopen($file, 'r');
    $records = array();
    while($data = fgetcsv($file)) {
        $key = $data[0];
        if(!isset($records[$key])) {
            $records[$key] = array();
        }
        $records[$key][] = array(0 => $data[1],
            1 => $data[2],
            2 => $data[3]);
        gc_collect_cycles();
    }
    fclose($file);
    unlink($file);
    return 1;
}

$file = __DIR__."/file.csv";
$files = glob(__DIR__.'/part_*');
if(sizeof($files)==0) {
    exec('split -l 1000 '.$file.' part_'); 
    $files = glob(__DIR__.'/part_*');
}
$children = array();
foreach($files AS $file) {
    if(($pid = pcntl_fork()) == 0) {
        exit(child_main($file));
    }
    else {
        $children[] = $pid;
    }
}
foreach($children as $pid) {
    $pid = pcntl_wait($status);
    if(pcntl_wifexited($status)) {
        $code = pcntl_wexitstatus($status);
        print "pid $pid returned exit code: $code'n";
    }
    else {
        print "$pid was unnaturally terminated'n";
    }
}
?>