PHP多维数组:用两个值的拼接替换所有键

7

我有一个 PHP 中的多维数组,外层数组包含数千个项目,每个项目本身都是一个包含"key1"、"key2"和"count"值的数组:

 myExistingArray (size=99999 VERY BIG)
      public 0 => 
        array (size=3)
          'key1' => string '15504' 
          'key2' => string '20'
          'count' => string '1'
      public 1 => 
        array (size=3)
          'key1' => string '15508' (length=5)
          'key2' => string '20' (length=2)
          'count' => string '2' (length=1)
      public 2 => 
        array (size=3)
          'key1' => string '15510' (length=5)
          'key2' => string '20' (length=2)
          'count' => string '5' (length=1)
....many more similar items

我想将此转换为一个非常简单的数组,其中来自“key1”和“key”的旧值连接成一个新键,指向相应的“count”值,如下所示:

  myNewArray (size=99999 VERY BIG)
      <key1>_<key2> => <count>
      15504_20 => string '1' (length=1)
      15508_20 => string '2' (length=1)
      15510_20 => string '5' (length=1)

对我来说性能非常重要,因为外部数组有几千个项目。PHP中有快速方法吗?我唯一得到的是一个简单的迭代,但这似乎对我而言太慢了:

// works but I am looking for a faster version
$myNewArray = array();
foreach ($myExistingArray as $item) {
  $myNewArray [$item["key1"]."_".$item["key1"]]=$item["count"];
}

编辑/ 根本问题

有些人指出,我的当前解决方案已经是O(n)了,并提到PHP中没有内置函数可以加速这个过程。

我从mysql数据库查询中获得“myExistingArray”。我基本上有工作对象,并希望按照它们的状态和事件ID对它们进行分组。类似于以下查询:

select count(job.id) as count, job.status as key1, job.event_id as key2
from job
group by job.status, job.event_id

我希望重新排列键值,以便稍后可以轻松访问具有特定状态的某个事件的工作计数。


尝试使用 array_column。 - ka_lin
1
"太慢"指的是什么?你的建议似乎是O(n),对于这种情况来说已经达到了最快的速度。 - Erik
数据最初来自哪里?也许您可以更改数据的存储方式或提供方式,从而避免一开始就需要处理数据。无论如何,如果这是不可能的,那么您现有的循环是最简单的方式,通常(包括此情况)意味着这是完成任务的最快方法。迭代所有数据并创建新数组将始终是一个O(n)操作,因为每个元素都需要被处理。 - Elias Van Ootegem
这是一个简单的数据库输出吗?如果您将两个键的连接串提供给SQL,对于大量行而言,这比在PHP中进行更快。类似于 SELECT CONCAT(key1,'_',key2) as key,count [...] 然后在php foreach中取消设置当前行以获得内存 :) - Bobot
另外,最终数组用于什么?如果您不将其用作映射,则有其他选项可以真正加快此过程。 - dave
@dave 我同意。这可能更像是一个XY问题。如何获取数据很可能是找到更好方法的关键。甚至数据的使用方式也可以为我们提供更好的解决问题的见解。更改查询、API调用和添加一些分页功能更有可能帮助OP。 - Elias Van Ootegem
4个回答

2
通常情况下,您可能会寻找array_walkarray_map函数来转换PHP中的数组,但不幸的是,它们都不能改变您想要转换的数组的键。 array_walk将保留键,但不会更改它们。因此,很遗憾,没有内置的函数可以做到您所要求的那样。

这应该是一个注释。无论如何,即使有一个内置函数,它在内部也必须执行与 OP 相同的操作。它仍然是一个 O(n) 的操作。如果将回调函数添加到其中,几乎肯定会比简单的 foreach 操作更慢。 - Elias Van Ootegem
一条注释可能会更好。我同意内置函数不会增加速度,但我假设OP的意思是要求一个更加面向函数式编程的方法,特别是因为没有可能提高空间利用率,这就是为什么我还提到了通常用于数组转换的函数。 - Gareth Parker

1

我进行了几次测试,结果几乎都相同。

Test 1:  [0.25861501693726]
Test 2:  [0.20804476737976]
Test 3:  [0.21039199829102]
Oldskool:[0.26545000076294]
Test 4:  [0.35072898864746]

对合并数组进行var_dump()操作会减慢程序速度(如预期所示),但如果将其存储在内存中,则数据处理起来并不太困难。

用于测试的PHP代码:

// Construct the raw data
$i = 0;
do {
    $raw[] = array('key1' => mt_rand(10000,99999), 'key2' => mt_rand(10,99), 'count' => $i);
} while(++$i < 100000);

// Test 1
$before = microtime(true);
foreach($raw as $k => $v) {
    $clean[$v['key1'].'_'.$v['key2']] = $v['count'];
}
$after = microtime(true);
echo 'Test 1:['.($after - $before).']<br />';

$clean = false;
$i = 0;

// Test 2
$before = microtime(true);
$max = count($raw);
do {
    $clean[$raw[$i]['key1'].'_'.$raw[$i]['key2']] = $raw[$i]['count'];
} while(++$i < $max);
$after = microtime(true);
echo 'Test 2:['.($after - $before).']<br />';

$clean = false;
$i = 0;

// Test 3
$before = microtime(true);
$max = count($raw);
for($i; $i < $max; $i++) {
    $clean[$raw[$i]['key1'].'_'.$raw[$i]['key2']] = $raw[$i]['count'];
}
$after = microtime(true);
echo 'Test 3:['.($after - $before).']<br />';

$clean = false;

// Test of Oldskool's suggestion
$before = microtime(true);
foreach (array_keys($raw) as $item) {
    $clean[$raw[$item]['key1'].'_'.$raw[$item]['key2']] = $raw[$item]['count'];
}
$after = microtime(true); 
echo 'Test Oldskool:['.($after - $before).']<br />';

$clean = false;
$i = 0;

// Test 4, just for fun
$before = microtime(true);
$max = count($raw);
do {
    $c = array_pop($raw[$i]);
    $clean[join('_', $raw[$i])] = $c;
} while(++$i < $max);
$after = microtime(true);
echo 'Test 4:['.($after - $before).']<br />';

编辑:增加了一个Oldskool示例的测试。


0

您可以将foreach更改为仅迭代键而不是整个子数组,方法如下:

foreach (array_keys($myExistingArray) as $item) {
    $myNewArray[$myExistingArray[$item]['key1'] . '_' . $myExistingArray[$item]['key2']] = $myExistingArray[$item]['count'];
}

这将为您带来一些轻微的速度优势(请参见此处(array_keys方法)和此处(您原始的方法)的时间比较)。在非常大的数组上,差异可能会变得更加明显。


不确定在大数组上使用array_keys是否会提高整体性能。调用array_keys会创建一个新数组,这意味着分配更多的内存并创建新的zval。很可能使用引用的方式foreach ($myExistingArray as &$arr)进行迭代仍然更快...无论如何,我认为这是微观优化,对于可能是XY问题的东西来说。 - Elias Van Ootegem

0
如果速度是问题,而且您没有将最终数组用作映射,我建议创建一个生成器,这样您就不必预先计算所有内容。
$myExistingArray = [ ... ];
class MyNewArrayIterator implements IteratorAggregate {
    protected $array;
    public function __construct(array $array) {
        $this->array = $array;
    }
    public function getIterator() {
        foreach ($this->array as $value) {
            yield $value['key1'] . '_' . $value['key2'] => $value['count'];
        }
    }
}

然后你可以这样做:

$myNewArray = new MyNewArrayIterator($myExistingArray);
foreach($myNewArray as $key => $value) {
    echo $key . ": " . $value;
}

这可能对你的使用情况有用,也可能没有。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接