在PHP中转置多维数组

88

如何在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()函数?

编辑:这不是作业。我只是想看看是否有更有创意的解决方案,而不是最显然的路线。但由于一些人抱怨这个问题太简单了,那么对于一个nth维数组,如何编写一个更通用的解决方案呢?

即如何编写一个函数,使得:

$foo[j][k][...][x][y][z] = $bar[z][k][...][x][y][j]

?(顺便说一句,我认为在这种情况下嵌套12个for循环不是最好的解决方案。)


3
@Calvin我知道这是很多年前的事了(11年!),不过你是否接受了任何答案?你是否注意到最受欢迎的答案基本上是错误的,因为它不支持单行[[1,2,...N]]?请查看沙盒以获得说明:http://sandbox.onlinephpfunctions.com/code/14ed8c26f65dae1b78b59473d5806dbdf74c7f92 - Onkeltem
此外,splat运算符无法解包字符串键。错误的证明:https://3v4l.org/1WSQH...哎呀,我刚意识到我在这个页面上一年前已经回答过这个问题了! - mickmackusa
14个回答

270
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);
}

20
array_unshift的参数为NULL,将值添加到数组的开头。因此,第一行将NULL插入数组的第一个值。下一行使用$array的所有条目作为参数调用array_map。因此,它与调用array_map(NULL,$array[0],$array[1],$array[2]等等)相同。在array_map文档中有一个细节:“此函数的有趣用法是构造一个数组的数组,可以通过使用NULL作为回调函数的名称轻松完成”。 - Jeremy Warne
15
如果索引是字符串类型,此函数不会保留它们,而是返回具有数值索引的转置矩阵。在这种情况下,flipDiagonally函数可以正常工作。无论如何,请给个赞以示简单易用。 - luso
7
当只有一行时会出现问题,例如 transpose([[1,2]]) 预期结果是 [[1],[2]],但实际结果是 [1,2]。 - chris
5
好的,请提供需要翻译的具体内容。 - Awlad Liton
4
为什么这样做有效?这个答案对我来说完全是晦涩的。 - danimal
显示剩余11条评论

83

使用2个循环。

function flipDiagonally($arr) {
    $out = array();
    foreach ($arr as $key => $subarr) {
        foreach ($subarr as $subkey => $subvalue) {
            $out[$subkey][$key] = $subvalue;
        }
    }
    return $out;
}

19
虽然Codler的答案更简洁,但我认为这种方法实际上是更好的,因为它更清楚地说明了发生了什么。如果一个有编程经验但不是PHP专家的人看这两个答案,他们会立即理解这个答案做了什么,但需要阅读文档中的细节才能理解另一个答案。+1 - hackartist
相反地,我发现这对于非数字键并不好用。例如 $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
1
@JohnK [0][1] 和 [2][1] 将变成 [1][0] 和 [1][2]。它翻转了键。我尝试了你的例子,它完全按照预期工作。我不确定你期望什么。 - OIS
有人对这两个解决方案进行过性能分析吗?如果 $arr 数组的数量非常大,Codler 的解决方案的可扩展性如何? - donquixote
3
@donquixote 的解决方案是错误的,非常错误。它不支持关联数组,并且在单行/列的微不足道的边缘情况下会失败:[[a,b,...,z]]. 它必须被撤销以避免混淆人们。 - Onkeltem

9

我想你指的是数组转置(列变行,行变列)。

这里有一个函数可以为您完成此操作(来源)

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;
} 

4

转置一个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];

这个方法可能有些hackish,也不是最好的解决方案,但是它有效。

基本上,它通过递归遍历数组,将当前索引累加到一个数组中。
一旦它到达所需的值,它会取出索引的“堆栈”,并将其反转,放入$out数组中。(有没有办法避免使用$temp数组?)


3

Codler的答案对于单行矩阵(例如[[1,2]])和空矩阵([])无效,必须特别处理:

function transpose(array $matrix): array {
    if (!$matrix) return [];
    return array_map(count($matrix) == 1 ? fn ($x) => [$x] : null, ...$matrix);
}

(注:PHP 7.4+语法,适用于旧版本的适应非常容易)

这仅适用于索引数组。 - mickmackusa

1

我也遇到了同样的问题。这是我想出来的解决方法:

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;
}

3
这个答案缺少简明易懂的英文解释。 - mickmackusa

1
我们可以通过使用两个 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>";

这与11年前发布的答案没有任何区别。 - Walf
它略有不同,因为它正在重新索引子数组。 - mickmackusa

1
我需要一个支持关联数组的转置函数:
    $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);
}

1
这看起来像是不必要的复杂化,因为OIS已经通过将一个循环嵌套在另一个循环中进行了演示。我没有看到在项目中使用这种技术的任何强制性理由。 - mickmackusa
array_map_join_array()可以是一个原生的PHP函数,因为在数组键上进行映射/连接是一种有用的模式。这是一种泛化的方式。 - quazardous

0
在我开始之前,我想再次感谢@quazardus发布的通用解决方案,用于转置任何二维关联(或非关联)数组!
由于我习惯尽可能地简洁编写代码,因此我进一步"最小化"了他的代码。这很可能不是每个人都喜欢的方式。但是,以防万一有人感兴趣的话,这是我的解决方案:
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))); 语言结构将具有较少的复杂性和更好的性能。 - mickmackusa

0
这是 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
    )
);

这并不是“无循环”,而是“函数式”的,换句话说,它是在没有语言结构的情况下进行迭代。 - mickmackusa

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