Транспонування багатовимірних масивів у PHP


74

Як би ви перевернули на 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- го виміру?

тобто як би ви написали функцію, щоб:

$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/…
Onkeltem

Крім того, оператор splat не може розпаковувати рядкові ключі. Доказ помилки: 3v4l.org/1WSQH ... на жаль , я щойно зрозумів, що сказав це більше року тому як відповідь на цій сторінці!
mickmackusa

Відповіді:


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

2
Хм ... це працює на мене, але я насправді не розумію, чому. Я бачу, що array_map викликається з $ array як параметри, тоді як null - це перший параметр. Але чому array_map поводиться так? Чому нуль як параметр тут навіть нормальний?
Якоб Рунге

20
NULL задається як параметр array_unshift, що додає значення до початку масиву. Отже, перший рядок вставляє NULL як перше значення масиву. Наступний рядок викликає array_map з усіма параметрами $ array. Отже, це те саме, що викликати array_map (NULL, $ array [0], $ array [1], $ array [2] тощо, тощо). У документації array_map є подробиця: "Цікавим використанням цієї функції є побудова масиву масивів, який можна легко виконати, використовуючи NULL як назву функції зворотного виклику"
Джеремі Уорн,

14
Ця функція не зберігає індекси, якщо вони мають тип String. Він повертає транспоновану матрицю з числовим індексом. У цьому випадку функція flipDiagonally чудово працює. У будь-якому разі голосуйте за простоту
luso

5
Це розбивається, коли є лише один рядок, наприклад транспонування ([[1,2]]) Очікується: [[1], [2]] Фактичне: [1,2]
chris

3
у відповіді має бути якийсь опис.
Awlad Liton

69

З 2 петлями.

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

17
Незважаючи на те, що відповідь Кодлера є більш стислою, я думаю, що насправді це кращий спосіб це зробити, тому що набагато зрозуміліше, що відбувається. Якби хтось із досвідченим кодуванням, але не гуру на 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

@JohnK [0] [1] та [2] [1] стануть [1] [0] та [1] [2]. Клацає клавішами. Я спробував ваші приклади, і це працює точно так, як задумано. Я не впевнений, що ти очікував.
OIS

Хтось представив два рішення? Наскільки добре шкала рішення Кодлера, якщо кількість ($ arr) дійсно висока?
donquixote

1
@donquixote Рішення Кодлера неправильне. Погано. Він не підтримує асоціативні масиви та не спрацьовує у тривіальному випадку ребра одного рядка / стовпця: [[a, b, ..., z]]. Це потрібно знецінити, щоб не заплутати людей.
Onkeltem

8

Я думаю, ви маєте на увазі транспонування масиву (стовпці стають рядками, рядки стають стовпцями).

Ось функція, яка робить це за вас (джерело) :

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

3

Транспонування 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

Мені потрібна була функція транспонування з підтримкою асоціативного масиву:

    $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

Я зіткнувся з тією ж проблемою. Ось що я придумав:

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

У цій відповіді бракує простого англійського пояснення.
mickmackusa

1

Якщо ви спробуєте розпакувати зразкові дані OP за допомогою оператора splat ( ...), ви згенеруєте:

Фатальна помилка: Помилка невпійманого: Не вдається розпакувати масив за допомогою рядкових ключів

Доказ

Щоб подолати цю помилку, зателефонуйте 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.


ps, перш ніж я дізнався про цей стенографічний синтаксис транспонування, я написав потворніший, багатослівніший функціональний транспонсер, який спричинив деяку критику .


1

Ось варіант Кодлера / Андреаса, який працює з асоціативними масивами. Дещо довше, алебез циклу чисто функціональний:

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

1

Ми можемо зробити це, використовуючи Two 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>";

0

Перш ніж почати, я хотів би подякувати ще раз @quazardus для розміщення його узагальнене рішення для будь-яких двох tranposing dimenional асоціативних (або неассоціатівное) масиву!

Оскільки я маю звичку писати свій код якомога коротше, я продовжував "мінімізувати" його код трохи далі. Це дуже ймовірно , НЕ буде на будь-який смак. Але на випадок, якщо комусь це буде цікаво, ось мій приклад щодо його рішення:

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. Мені довелося скинути деякі масиви в 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

Ось 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

Демо .


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

Спробуйте так


1
Це виглядає як незрозуміла точна копія рішення @ OIS. У цій відповіді немає додаткової вартості.
mickmackusa
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.