在数组的列上应用array_map


Apply array_map on column of the array

我试图找到一个加快PHP进程的解决方案。我们在特定页面上遇到了执行时间问题。我们有一个大约有10.000行的数组,我们需要在该数组的一些列上应用几个回调函数。

在一个数组上只对几个列应用回调的最佳方式和最快执行方式是什么。

<?php
$records = [
    ['id' => 2135, 'first_name' => 'John', 'price' => 1000, 'unit' => 5, 'discount' => 30],
    ['id' => 3245, 'first_name' => 'Sally', 'price' => 2000, 'unit' => 8, 'discount' => 80],
    ['id' => 5342, 'first_name' => 'Jane', 'price' => 4000, 'unit' => 5, 'discount' => 34],
    ['id' => 5623, 'first_name' => 'Peter', 'price' => 1500, 'unit' => 4, 'discount' => 25]
];
function simpleMultiply($value)
{
    return $value * 2;
}
$applyToColumn = ['price','unit','discount'];
// $expectedRecords = array_map('simpleMultiply', array_column($records, 'id'));

$expectedRecords = [
    ['id' => 2135, 'first_name' => 'John', 'price' => 2000, 'unit' => 10, 'discount' => 60],
    ['id' => 3245, 'first_name' => 'Sally', 'price' => 4000, 'unit' => 16, 'discount' => 160],
    ['id' => 5342, 'first_name' => 'Jane', 'price' => 8000, 'unit' => 10, 'discount' => 68],
    ['id' => 5623, 'first_name' => 'Peter', 'price' => 3000, 'unit' => 8, 'discount' => 50]
];
?>

array_column是一个昂贵的操作,如果数组很大,因为它必须创建一个全新的数组。对不同的列重复调用它将使开销成倍增加。我建议您只使用一个简单的foreach循环。

$expectedRecords = array();
foreach ($records as $r) {
    foreach ($applyToColumn as $col) {
        $r[$col] = simpleMultiply($r[$col]);
    }
    $expectedRecords[] = $r;
}

如果可以修改原始$records而不是创建新的$expectedRecords,则可以在第一个foreach中使用引用。

foreach ($records as &$r) {
    foreach ($applyToColumn as $col) {
        $r[$col] = simpleMultiply($r[$col]);
    }
}

您可以将结果集包装在迭代器(或生成器,看起来与迭代器非常相似)中,该迭代器在将当前行作为迭代结果返回之前对其进行修改,例如

<?php
class Foo extends IteratorIterator {
    public function current() {
        $rv = parent::current();
        if ( $rv ) {
            // ... and more checks here ....
            // this example just assumes the fields/elements exist
            $rv['x'] *= 17;
            return $rv;
        }
    }
}
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'localonly', 'localonly', array(
    PDO::ATTR_EMULATE_PREPARES=>false,
    PDO::MYSQL_ATTR_DIRECT_QUERY=>false,
    PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION
));
setup($pdo);

// the point is not to have the complete result set in memory
// see: e.g. php.net/mysqlinfo.concepts.buffering (or whatever database you use ;-) )
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
$result = new Foo( $pdo->query('SELECT x,y FROM sofoo') );
foreach( $result as $row ) {
    echo join(', ', $row), PHP_EOL;
}

function setup($pdo) {
    $pdo->exec('
        CREATE TEMPORARY TABLE sofoo(
            id int auto_increment, # I just throw this in casually....
            x int,
            y int,
            primary key(id)
        )
    ');
    $stmt = $pdo->prepare('INSERT INTO sofoo (x,y) VALUES (?,?)');
    foreach( range(1,5) as $x ) {
        $stmt->execute( array($x, $x*13) );
    }
}

或者(没有大部分样板,使用生成器-并立即调用匿名函数)

$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
$result = (function($iter) {
    foreach( $iter as $row ) {
        $row['x'] *= 17;
        yield $row;
    }
})($pdo->query('SELECT x,y FROM sofoo') );

foreach( $result as $row ) {
    echo join(', ', $row), PHP_EOL;
}

你可以任意复杂地构建这个"系统"。也许将字段名称和函数传递给生成器,以修改元素,如

$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
$result = my_generator(
    $pdo->query('SELECT x,y FROM sofoo'),
    array('x'=>function($e) { return $e*17; })
);
foreach( $result as $row ) {
    echo join(', ', $row), PHP_EOL;
}
function my_generator($iter, $modifiers) {
    foreach( $iter as $row ) {
        foreach( $modifiers as $field=>$func) {
            $row[$field] = $func($row[$field]);
        }
        yield $row;
    }
}

或者添加一些链接机制,以便多个函数可以修改同一字段。或者将整行传递给函数,以便它们也可以更改结构。或或