85

How would you flip 90 degrees (transpose) a multidimensional array in PHP? For example:

// 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"
}

How would you implement flipDiagonally()?

Edit: this is not homework. I just want to see if any SOers have a more creative solution than the most obvious route. But since a few people have complained about this problem being too easy, what about a more general solution that works with an nth dimension array?

i.e. How would you write a function so that:

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

?(ps. I don't think 12 nested for loops is the best solution in this case.)

dreftymac
  • 31,404
  • 26
  • 119
  • 182
Calvin
  • 4,559
  • 1
  • 25
  • 23
  • 3
    @Calvin I know it was many years ago (11!), yet.. have you accepted any answer or not? Have you noticed that the most popular answer is basically wrong, because doesn't support single-rows [[1,2,...N]]? Check out the sandbox for the illustration: http://sandbox.onlinephpfunctions.com/code/14ed8c26f65dae1b78b59473d5806dbdf74c7f92 – Onkeltem Feb 03 '20 at 01:22
  • Furthermore the splat operator cannot unpack string keys. Proof of the error: https://3v4l.org/1WSQH ...whoops, I just realized that I said this over a year ago as an answer on this page! – mickmackusa Jun 23 '20 at 13:20

13 Answers13

267
function transpose($array) {
    array_unshift($array, null);
    return call_user_func_array('array_map', $array);
}

Or if you're using PHP 5.6 or later:

function transpose($array) {
    return array_map(null, ...$array);
}
Andrea
  • 19,134
  • 4
  • 43
  • 65
Codler
  • 10,951
  • 6
  • 52
  • 65
  • 2
    Hm… it's working for me, but I don't really understand why. I see that array_map is called with $array as parameters, whereas null is the first parameter. But why does array_map behave this way? Why is null as a parameter even ok here? – Jakob Runge Mar 21 '12 at 23:57
  • 20
    NULL is given as the parameter to array_unshift, which adds a value to the start of the array. So, the first line inserts NULL as the first value of the array. The next line calls array_map with all the entries of $array as the parameters. So it's the same as calling array_map(NULL, $array[0], $array[1], $array[2], etc etc). In the array_map documentation there's a detail: "An interesting use of this function is to construct an array of arrays, which can be easily performed by using NULL as the name of the callback function" – Jeremy Warne Apr 26 '12 at 23:56
  • 15
    This function doesn't keep the indexes if they are of type String. It returns the transposed matrix with numeric index. The flipDiagonally function works fine in this case. Upvote anyways for simplicity – luso Aug 24 '12 at 14:45
  • I'm actually having problems with this method. The keys (`a`, `b` and `c`) are lost/now numeric. EDIT: I see @luso already mentioned this issue. – Koen. Jan 23 '14 at 09:04
  • What is the complexity of this function? Is it the same as flipDiagonally or better? – Ynhockey Aug 31 '14 at 06:14
  • 7
    This breaks down when there is only one row e.g. transpose( [[1,2]] ) Expected: [[1],[2]] Actual: [1,2] – chris Mar 16 '15 at 21:13
  • Quick test for those who do not believe their eyes: `var_dump([[1,3],[2,4]] == call_user_func(function ($array) { array_unshift($array, null); return call_user_func_array('array_map', $array); }, [[1,2],[3,4]]));` – sanmai Mar 21 '15 at 03:38
  • 5
    should be some description in the answer. – Awlad Liton Oct 14 '15 at 11:24
  • 3
    but why does this work? this answer is totally opaque to me – danimal Nov 26 '15 at 12:21
  • this function gives me a `PHP Warning: array_map(): Argument #6 should be an array in myFile.php on line 10...` - Is this a fault of the function or my own code? – brietsparks Dec 07 '15 at 23:29
  • @luso I've posted a solution with full associative array support below – quazardous May 24 '17 at 09:04
  • @luso to also keep the indices use `array_combine(array_keys($array[0]), array_map(null, ...$array))` for a 2d array – David Boho Aug 23 '17 at 14:44
  • 1
    `array_map(null, ...$array);` produces incorrect results when `$array` contains 0 or 1 elements. Neat bit of code, wrong usage. – martixy Mar 18 '18 at 19:10
  • I confirm that this code DOES NOT work when $array has only ONE element. Which makes this solution WRONG. – Onkeltem Feb 03 '20 at 01:03
  • 1
    This does not work for me against the user's input – Carlos Rodrigues Jun 30 '21 at 21:28
  • @CarlosRodrigues correct. These techniques have version-specific functionality to avoid breakage. Even if you have a PHP version that doesn't break on the asker's sample input, you lose the associative relationships. I endorse OIS's technique because it is stable, reliable, and performant. DavidBoho's suggestion loses associative relationship on the nested data. – mickmackusa Mar 31 '22 at 03:48
81

With 2 loops.

function flipDiagonally($arr) {
    $out = array();
    foreach ($arr as $key => $subarr) {
        foreach ($subarr as $subkey => $subvalue) {
            $out[$subkey][$key] = $subvalue;
        }
    }
    return $out;
}
OIS
  • 9,833
  • 3
  • 32
  • 41
  • 19
    Even though Codler's answer is more concise, I think that this is actually the better way to do it because it is so much more clear what is going on. If someone with coding experience but not a php guru were to look at the two answers, they would immediately understand what this one does but would have to read the fine print of the documentation to follow the other. +1 – hackartist Feb 03 '12 at 23:05
  • On the contrary, I've found that this doesn't fare so well with non-numeric keys. For example `$test = array(array('a'=>1, 'b'=>2,'c'=>3), array(4,5,6), array(7,8,9));`: it creates a single-element array for each value with a non-numeric key. With specified numeric keys (e.g., `$test = array(array(4,5,6), array(11=>1, 12=>2, 13=>3), array(7,8,9));`), it does some weirdness. While by all rights this *should* work, I think we need a better solution! – JohnK Jul 14 '14 at 20:01
  • 1
    @JohnK [0][1] and [2][1] will become [1][0] and [1][2]. It flips the keys. I tried your examples and it works exactly as intended. Im not sure what you expected. – OIS Aug 04 '14 at 13:45
  • Has anyone profiled the two solutions? How well does the Codler's solution scale if count($arr) is really high? – donquixote Jul 05 '16 at 15:17
  • 3
    @donquixote Codler's solution is wrong. Badly. It doesn't support associative arrays and fails on the trivial edge case of single row/column: [[a,b,...,z]]. It must be devalued to not confuse people. – Onkeltem Feb 03 '20 at 01:13
9

I think you're referring to the array transpose (columns become rows, rows become columns).

Here is a function that does it for you (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;
} 
Aziz
  • 20,065
  • 8
  • 63
  • 69
4

Transposing an N-dimensional array:

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

Really hackish, and probably not the best solution, but hey it works.

Basically it traverses the array recursively, accumulating the current indicies in an array.
Once it gets to the referenced value, it takes the "stack" of indices and reverses it, putting it into the $out array. (Is there a way of avoiding use of the $temp array?)

v3.
  • 2,003
  • 15
  • 18
3

Codler's answer fails for a single-row matrix (e.g. [[1,2]]) and also for the empty matrix ([]), which must be special-cased:

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

(note: PHP 7.4+ syntax, easy enough to adapt for older versions)

1

I got confronted with the same problem. Here is what i came up with:

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

I needed a transpose function with support for associative array:

    $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],
    ];

And the way back:

    $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],
    ];

The array_unshift trick did not work NOR the array_map...

So I've coded a array_map_join_array function to deal with record keys association:

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

and array_transpose became obvious:

function array_transpose(array $matrix)
{
    return \array_map_join_array(function ($key, $items) {
        return $items;
    }, $matrix);
}
quazardous
  • 846
  • 10
  • 15
  • 1
    This looks like unnecessary convolution for what OIS has demonstrated by nesting a loop inside another loop. I don't see any compelling reason to use this technique in a project. – mickmackusa Mar 31 '22 at 03:55
  • array_map_join_array() could be a native PHP function, because mapping/joining on array keys is a usefull pattern. it was some kind of generalisation. – quazardous Aug 24 '23 at 18:29
1

We can do this by using Two foreach. Traveling one array and another array to create new array
Like This:

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

Check The Output

echo "<pre>";
print_r($newFoo);
echo "</pre>";
Mehedi Hasan
  • 61
  • 1
  • 6
0

Before I start, I'd like to say thanks again to @quazardus for posting his generalised solution for tranposing any two dimenional associative (or non-associative) array!

As I am in the habit of writing my code as tersely as possible I went on to "minimizing" his code a little further. This will very likely not be to everybody's taste. But just in case anyone should be interested, here is my take on his solution:

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

Now, analogous to the PHP standard function array_map(), when you call

arrayMap(null,$b);

you will get the desired transposed matrix.

Carsten Massmann
  • 26,510
  • 2
  • 22
  • 43
  • There are certainly more direct/concise/efficient ways to gather the unique second level keys. For instance: `$keys = array_keys(array_merge(...array_values($arrays)));` a language construct will have less complexity and even better performance. – mickmackusa Mar 15 '20 at 12:48
0

This is another way to do the exact same thing which @codler s answer does. I had to dump some arrays in csv so I used the following function:

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

Test and output reference: https://tutes.in/how-to-transpose-an-array-in-php-with-irregular-subarray-size/

th3pirat3
  • 559
  • 8
  • 19
0

Here is array_walk way to achieve this,

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.

Rahul
  • 18,271
  • 7
  • 41
  • 60
0

Here's a variation of Codler/Andreas's solution that works with associative arrays. Somewhat longer but loop-less purely functional:

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

Example:

<?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
  • 43,625
  • 12
  • 83
  • 136
tomkyle
  • 9
  • 3
-1

Variant of Codler's answer with proper support of empty array and 1x1 array:

function transpose($a) {
    if (count($a) < 2) return $a;
    return array_map(null, ...$a);
}
Grzegorz Adam Kowalski
  • 5,243
  • 3
  • 29
  • 40