Транспонирование многомерных массивов в PHP - PullRequest
67 голосов
/ 28 апреля 2009

Как бы вы перевернули 90 градусов (транспонировали) многомерного массива в PHP? Например:

// 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 более креативное решение, чем самый очевидный путь. Но поскольку несколько человек жаловались на то, что эта проблема слишком проста, как насчет более общего решения, которое работает с массивом измерений n th ?

т.е.. Как бы вы написали функцию так:

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

? (Ps. Я не думаю, что 12 вложенных for loops - лучшее решение в этом случае.)

Ответы [ 13 ]

241 голосов
/ 06 августа 2010
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);
}
62 голосов
/ 28 апреля 2009

с 2 петлями.

function flipDiagonally($arr) {
    $out = array();
    foreach ($arr as $key => $subarr) {
        foreach ($subarr as $subkey => $subvalue) {
            $out[$subkey][$key] = $subvalue;
        }
    }
    return $out;
}
8 голосов
/ 28 апреля 2009

Я думаю, вы ссылаетесь на массив transpose (столбцы становятся строками, строки становятся столбцами).

Вот функция, которая делает это за вас (источник) :

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;
} 
2 голосов
/ 28 апреля 2009

Транспонирование 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?)

1 голос
/ 19 апреля 2019

Вот вариант решения Кодлера / Андреаса , который работает с ассоциативными массивами. Несколько длиннее, но без петель:

<?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
    )
);
1 голос
/ 23 мая 2017

Мне нужна была функция транспонирования с поддержкой ассоциативного массива:

    $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 голос
/ 16 февраля 2016

Я столкнулся с той же проблемой. Вот что я придумал:

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;
}
0 голосов
/ 07 апреля 2019

Если вы попытаетесь распаковать образцы данных OP с помощью оператора splat (...), вы получите:

Неустранимая ошибка: необработанная ошибка: невозможно распаковать массив со строковыми ключами

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',
  ),
)

Обратите внимание на уровень заботы, проявляемый функцией (сравнимый с обработчиками багажа, которые вывозят ваш багаж из живота самолета). Не обращают внимания на идентификаторы исходных значений подмассива (и не имеет значения, если бы 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',
  ),
)

Как показано выше, если вы хотите быть уверены, что извлекли ВСЕ данные из входного массива, вам нужно написать логику сложения, чтобы доставить все уникальные идентификаторы столбцов в цикл foreach.


p.s. прежде чем я узнал об этом сокращенном синтаксисе транспонирования, я написал более уродливый, более подробный функциональный транспозитор, который выдержал некоторую критику .

0 голосов
/ 02 апреля 2019

Вот способ 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

Демо .

0 голосов
/ 05 марта 2019

Это еще один способ сделать то же самое, что делает ответ @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/

...