it-swarm.com.ru

Транспонирование многомерных массивов в PHP

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

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

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

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

65
Calvin
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);
}
239
Codler

С 2 петлями.

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

Я думаю, что вы имеете в виду массив 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;
} 
8
Aziz

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

2
v3.

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

<?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
tomkyle

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

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
José Trindade

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

    $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 не сработал NOR 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
quazardous

Используйте как это

<?php
$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' 
    )
);

echo "<pre>"; 

 $i=0;
 foreach ($foo as $val)
   { $i++;
       $array[$i] = array_column($foo, $i);    

   }
   print_r($array);

?>

Результат:

Array
(
    [1] => Array
        (
            [0] => a1
            [1] => b1
            [2] => c1
        )

    [2] => Array
        (
            [0] => a2
            [1] => b2
            [2] => c2
        )

    [3] => Array
        (
            [0] => a3
            [1] => b3
            [2] => c3
        )

)
0
Mani
<?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);

Попробуйте как это

0
MrCharif

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

Фатальная ошибка: Uncaught Ошибка: невозможно распаковать массив с ключами строки

Доказательство

Чтобы устранить эту ошибку, вызовите 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.


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

0
mickmackusa

Прежде чем начать, я хотел бы сказать спасибо еще раз @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);

вы получите желаемую транспонированную матрицу. 

0
cars10m

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

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

0
th3pirat3