在 PHP 中转置多维数组
Posted
技术标签:
【中文标题】在 PHP 中转置多维数组【英文标题】:Transposing multidimensional arrays in PHP 【发布时间】:2010-10-22 06:53:08 【问题描述】:如何在 php 中将多维数组翻转 90 度(转置)?例如:
// Start with this array
$foo = array(
'a' => array(
1 => 'a1',
2 => 'a2',
3 => 'a3'
),
'b' => array(
1 => 'b1',
2 => 'b2',
3 => 'b3'
),
'c' => array(
1 => 'c1',
2 => 'c2',
3 => 'c3'
)
);
$bar = flipDiagonally($foo); // Mystery function
var_dump($bar[2]);
// Desired output:
array(3)
["a"]=>
string(2) "a2"
["b"]=>
string(2) "b2"
["c"]=>
string(2) "c2"
你将如何实现flipDiagonally()
?
编辑:这不是家庭作业。我只是想看看是否有任何 SOers 有比最明显的路线更有创意的解决方案。但是由于有些人抱怨这个问题太简单了,那么使用 nth 维数组的更通用的解决方案呢?
即你将如何编写一个函数:
$foo[j][k][...][x][y][z] = $bar[z][k][...][x][y][j]
?(ps。我不认为 12 嵌套 for loops
在这种情况下是最好的解决方案。)
【问题讨论】:
@Calvin 我知道那是很多年前的事了(11 岁!),但是.. 你接受过任何答案吗?您是否注意到最流行的答案基本上是错误的,因为不支持单行 [[1,2,...N]]?查看沙盒的插图:sandbox.onlinephpfunctions.com/code/… 此外,splat 运算符无法解压缩字符串键。错误证明:3v4l.org/1WSQH ...哎呀,我才意识到一年多前我在这个页面上说过这个作为答案! 【参考方案1】:对于单行矩阵(例如[[1,2]]
)和空矩阵([]
),Codler 的回答都失败,必须是特殊情况:
function transpose(array $matrix): array
if (!$matrix) return [];
return array_map(count($matrix) == 1 ? fn ($x) => [$x] : null, ...$matrix);
(注意:PHP 7.4+ 语法,很容易适应旧版本)
【讨论】:
【参考方案2】:我们可以通过使用两个 foreach 来做到这一点。遍历一个数组和另一个数组以创建新数组像这样:
$foo = array(
'a' => array(
1 => 'a1',
2 => 'a2',
3 => 'a3'
),
'b' => array(
1 => 'b1',
2 => 'b2',
3 => 'b3'
),
'c' => array(
1 => 'c1',
2 => 'c2',
3 => 'c3'
)
);
$newFoo = [];
foreach($foo as $a => $k)
foreach($k as $i => $j)
$newFoo[$i][]= $j;
检查输出
echo "<pre>";
print_r($newFoo);
echo "</pre>";
【讨论】:
【参考方案3】:这是Codler/Andreas's solution 的一个变体,适用于关联数组。有点长,但 无循环 纯粹是功能性的:
<?php
function transpose($array)
$keys = array_keys($array);
return array_map(function($array) use ($keys)
return array_combine($keys, $array);
, array_map(null, ...array_values($array)));
例子:
<?php
$foo = array(
"fooA" => [ "a1", "a2", "a3"],
"fooB" => [ "b1", "b2", "b3"],
"fooC" => [ "c1", "c2", "c3"]
);
print_r( transpose( $foo ));
// Output like this:
Array (
[0] => Array (
[fooA] => a1
[fooB] => b1
[fooC] => c1
)
[1] => Array (
[fooA] => a2
[fooB] => b2
[fooC] => c2
)
[2] => Array (
[fooA] => a3
[fooB] => b3
[fooC] => c3
)
);
【讨论】:
这不是“无循环”,它只是“功能性”,换句话说,它是在没有语言结构的情况下进行迭代。【参考方案4】:如果您尝试使用 splat 运算符 (...
) 解压 OP 的示例数据,您将生成:
致命错误:未捕获的错误:无法使用字符串键解包数组
Proof
为了克服这个错误,在解包之前调用array_values()
索引第一级键。
var_export(array_map(null, ...array_values($foo)));
输出:
array (
0 =>
array (
0 => 'a1',
1 => 'b1',
2 => 'c1',
),
1 =>
array (
0 => 'a2',
1 => 'b2',
2 => 'c2',
),
2 =>
array (
0 => 'a3',
1 => 'b3',
2 => 'c3',
),
)
关于使用此技术转置的另一个特性/惊喜是,当子数组的大小不同时,将生成 null
元素......但可能不是您所期望的。
来自这样的示例数据:
$foo = array(
'a' => array(
1 => 'a1',
2 => 'a2'
),
'b' => array(
1 => 'b1',
3 => 'b3'
),
'c' => array(
1 => 'c1',
2 => 'c2',
3 => 'c3'
)
);
输出是:
array (
0 =>
array (
0 => 'a1',
1 => 'b1',
2 => 'c1',
),
1 =>
array (
0 => 'a2',
1 => 'b3',
2 => 'c2',
),
2 =>
array (
0 => NULL,
1 => NULL,
2 => 'c3',
),
)
请注意该功能所表现出的水平护理(类似于将行李从机腹中取出的行李搬运工)。不注意原始子数组值的 id(如果1
、2
、&3
是 x
、y
、&z
,则无关紧要);任何从传送带上掉下来的东西都会被扔到最低的可用插槽中。
这种行为在交付完整矩阵时是一致且可靠的。 foreach()
循环替代方案本身不会从不同大小的子数组中传递 null
元素,并且在大多数实现中,它访问所有子数组值的能力取决于第一个子数组的长度。
$foo = array(
'a' => array(
1 => 'a1',
2 => 'a2'
),
'b' => array(
1 => 'b1',
),
'c' => array(
1 => 'c1',
2 => 'c2',
3 => 'c3'
)
);
foreach (current($foo) as $column => $not_used)
$result[] = array_column($foo, $column);
var_export($result);
输出:
array (
0 =>
array (
0 => 'a1',
1 => 'b1',
2 => 'c1',
),
1 =>
array (
0 => 'a2',
1 => 'c2',
),
)
如上所示,如果您想确保从输入数组中提取了所有数据,则必须编写加法逻辑以将所有唯一的列 ID 传递到 foreach 循环。
附言在我了解这种速记转置语法之前,我写了an uglier, more verbose functional transposer that copped some criticism。
【讨论】:
【参考方案5】:这是实现这一点的 array_walk 方式,
function flipDiagonally($foo)
$temp = [];
array_walk($foo, function($item,$key) use(&$temp)
foreach($item as $k => $v)
$temp[$k][$key] = $v;
);
return $temp;
$bar = flipDiagonally($foo); // Mystery function
Demo.
【讨论】:
【参考方案6】:这是与@codler 的回答完全相同的另一种方法。我不得不在 csv 中转储一些数组,所以我使用了以下函数:
function transposeCsvData($data)
$ct=0;
foreach($data as $key => $val)
//echo count($val);
if($ct< count($val))
$ct=count($val);
//echo $ct;
$blank=array_fill(0,$ct,array_fill(0,count($data),null));
//print_r($blank);
$retData = array();
foreach ($data as $row => $columns)
foreach ($columns as $row2 => $column2)
$retData[$row2][$row] = $column2;
$final=array();
foreach($retData as $k=>$aval)
$final[]=array_replace($blank[$k], $aval);
return $final;
测试和输出参考:https://tutes.in/how-to-transpose-an-array-in-php-with-irregular-subarray-size/
【讨论】:
【参考方案7】:我需要一个支持关联数组的转置函数:
$matrix = [
['one' => 1, 'two' => 2],
['one' => 11, 'two' => 22],
['one' => 111, 'two' => 222],
];
$result = \array_transpose($matrix);
$trans = [
'one' => [1, 11, 111],
'two' => [2, 22, 222],
];
还有回来的路:
$matrix = [
'one' => [1, 11, 111],
'two' => [2, 22, 222],
];
$result = \array_transpose($matrix);
$trans = [
['one' => 1, 'two' => 2],
['one' => 11, 'two' => 22],
['one' => 111, 'two' => 222],
];
array_unshift
的把戏不起作用,array_map
...
所以我编写了一个array_map_join_array
函数来处理记录键关联:
/**
* Similar to array_map() but tries to join values on intern keys.
* @param callable $callback takes 2 args, the intern key and the list of associated values keyed by array (extern) keys.
* @param array $arrays the list of arrays to map keyed by extern keys NB like call_user_func_array()
* @return array
*/
function array_map_join_array(callable $callback, array $arrays)
$keys = [];
// try to list all intern keys
array_walk($arrays, function ($array) use (&$keys)
$keys = array_merge($keys, array_keys($array));
);
$keys = array_unique($keys);
$res = [];
// for each intern key
foreach ($keys as $key)
$items = [];
// walk through each array
array_walk($arrays, function ($array, $arrKey) use ($key, &$items)
if (isset($array[$key]))
// stack/transpose existing value for intern key with the array (extern) key
$items[$arrKey] = $array[$key];
else
// or stack a null value with the array (extern) key
$items[$arrKey] = null;
);
// call the callback with intern key and all the associated values keyed with array (extern) keys
$res[$key] = call_user_func($callback, $key, $items);
return $res;
array_transpose
变得显而易见:
function array_transpose(array $matrix)
return \array_map_join_array(function ($key, $items)
return $items;
, $matrix);
【讨论】:
【参考方案8】:在开始之前,我想再次感谢 @quaazardus 发布他的通用解决方案,用于转置任何二维关联(或非关联)数组!
由于我习惯于尽可能简洁地编写我的代码,因此我继续进一步“最小化”他的代码。这很可能 不符合每个人的口味。但以防万一有人感兴趣,这是我对他的解决方案的看法:
function arrayMap($cb, array $arrays) // $cb: optional callback function
$keys = [];
array_walk($arrays, function ($array) use (&$keys)
$keys = array_merge($keys, array_keys($array)); );
$keys = array_unique($keys); $res = [];
foreach ($keys as $key)
$items = array_map(function ($arr) use ($key)
return isset($arr[$key]) ? $arr[$key] : null; ,$arrays);
$res[$key] = call_user_func(
is_callable($cb) ? $cb
: function($k, $itms)return $itms;,
$key, $items);
return $res;
现在,类似于 PHP 标准函数 array_map()
,当您调用时
arrayMap(null,$b);
您将获得所需的转置矩阵。
【讨论】:
当然有更直接/简洁/有效的方法来收集唯一的二级密钥。例如:$keys = array_keys(array_merge(...array_values($arrays)));
语言结构的复杂性更低,性能更好。【参考方案9】:
<?php
$tableau_init = [
[
"prenom" => "med",
"age" => 1
],
[
"prenom" => "hassan",
"age" => 2
],
[
"prenom" => "ali",
"age" => 3
]
];
function transpose($tableau)
$out = array();
foreach ($tableau as $key => $value)
foreach ($value as $subKey => $subValue)
$out[$subKey][$key] = $subValue;
echo json_encode($out);
transpose($tableau_init);
这样试试
【讨论】:
这看起来像是 @OIS 解决方案的无法解释的精确复制品。这个答案没有任何附加值。【参考方案10】:我遇到了同样的问题。这是我想出的:
function array_transpose(array $arr)
$keys = array_keys($arr);
$sum = array_values(array_map('count', $arr));
$transposed = array();
for ($i = 0; $i < max($sum); $i ++)
$item = array();
foreach ($keys as $key)
$item[$key] = array_key_exists($i, $arr[$key]) ? $arr[$key][$i] : NULL;
$transposed[] = $item;
return $transposed;
【讨论】:
这个答案缺少简单的英文解释。【参考方案11】:function transpose($array)
array_unshift($array, null);
return call_user_func_array('array_map', $array);
或者,如果您使用的是 PHP 5.6 或更高版本:
function transpose($array)
return array_map(null, ...$array);
【讨论】:
NULL 作为参数提供给array_unshift,它在数组的开头添加一个值。因此,第一行插入 NULL 作为数组的第一个值。下一行以 $array 的所有条目作为参数调用 array_map。所以它与调用 array_map(NULL, $array[0], $array[1], $array[2], etc etc 等) 相同。在 array_map 文档中有一个细节:“这个函数的一个有趣的用途是构造一个数组数组,这可以很容易地通过使用 NULL 作为回调函数的名称来执行” 如果索引是字符串类型,此函数不会保留索引。它返回带有数字索引的转置矩阵。在这种情况下,flipDiagonally 函数可以正常工作。为了简单起见,无论如何都要投票 当只有一行时这会崩溃,例如transpose( [[1,2]] ) 预期:[[1],[2]] 实际:[1,2] 应该是答案中的一些描述。 但是为什么会这样呢?这个答案对我来说完全不透明【参考方案12】:转置一个 N 维数组:
function transpose($array, &$out, $indices = array())
if (is_array($array))
foreach ($array as $key => $val)
//push onto the stack of indices
$temp = $indices;
$temp[] = $key;
transpose($val, $out, $temp);
else
//go through the stack in reverse - make the new array
$ref = &$out;
foreach (array_reverse($indices) as $idx)
$ref = &$ref[$idx];
$ref = $array;
$foo[1][2][3][3][3] = 'a';
$foo[4][5][6][5][5] = 'b';
$out = array();
transpose($foo, $out);
echo $out[3][3][3][2][1] . ' ' . $out[5][5][6][5][4];
真的很老套,可能不是最好的解决方案,但它确实有效。
基本上它递归地遍历数组,将当前索引累积到一个数组中。 一旦它到达引用的值,它就会获取索引的“堆栈”并将其反转,将其放入 $out 数组中。 (有没有办法避免使用 $temp 数组?)
【讨论】:
【参考方案13】:我认为您指的是数组transpose(列变为行,行变为列)。
这是一个为你做的函数(source):
function array_transpose($array, $selectKey = false)
if (!is_array($array)) return false;
$return = array();
foreach($array as $key => $value)
if (!is_array($value)) return $array;
if ($selectKey)
if (isset($value[$selectKey])) $return[] = $value[$selectKey];
else
foreach ($value as $key2 => $value2)
$return[$key2][$key] = $value2;
return $return;
【讨论】:
【参考方案14】:有 2 个循环。
function flipDiagonally($arr)
$out = array();
foreach ($arr as $key => $subarr)
foreach ($subarr as $subkey => $subvalue)
$out[$subkey][$key] = $subvalue;
return $out;
【讨论】:
尽管 Codler 的回答更简洁,但我认为这实际上是更好的方法,因为它更清楚发生了什么。如果有编码经验但不是 php 大师的人要查看这两个答案,他们会立即理解这个答案的作用,但必须阅读文档的细则才能遵循另一个答案。 +1 相反,我发现这对于非数字键来说效果不佳。例如$test = array(array('a'=>1, 'b'=>2,'c'=>3), array(4,5,6), array(7,8,9));
:它使用非数字键为每个值创建一个单元素数组。使用指定的数字键(例如,$test = array(array(4,5,6), array(11=>1, 12=>2, 13=>3), array(7,8,9));
),它会有些奇怪。虽然这应该有效,但我认为我们需要一个更好的解决方案!
@JohnK [0][1] 和 [2][1] 将变为 [1][0] 和 [1][2]。它翻转键。我试过你的例子,它完全按照预期工作。我不确定你的预期。
有人介绍过这两种解决方案吗?如果 count($arr) 真的很高,Codler 的解决方案规模如何?
@donquixote Codler 的解决方案是错误的。很糟糕。它不支持关联数组,并且在单行/列的琐碎边缘情况下失败:[[a,b,...,z]]。一定要贬值才不会迷惑人。以上是关于在 PHP 中转置多维数组的主要内容,如果未能解决你的问题,请参考以下文章