Transponieren mehrdimensionaler Arrays in PHP

74

Wie würden Sie ein mehrdimensionales Array in PHP um 90 Grad drehen (transponieren)? Zum Beispiel:

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

Wie würden Sie implementieren flipDiagonally()?

Bearbeiten: Dies ist keine Hausaufgabe. Ich möchte nur sehen, ob SOer eine kreativere Lösung haben als der naheliegendste Weg. Aber da sich einige Leute darüber beschwert haben, dass dieses Problem zu einfach ist, was ist mit einer allgemeineren Lösung, die mit einem Array der n- ten Dimension funktioniert ?

dh wie würden Sie eine Funktion schreiben, damit:

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

? (ps. Ich denke nicht, dass 12 verschachtelt for loopsdie beste Lösung in diesem Fall ist.)

Calvin
quelle
@Calvin Ich weiß, es war vor vielen Jahren (11!), Aber .. hast du eine Antwort akzeptiert oder nicht? Haben Sie bemerkt, dass die beliebteste Antwort grundsätzlich falsch ist, weil sie keine einzeiligen [[1,2, ... N]] unterstützt? Schauen Sie sich die Sandbox für die Abbildung an: sandbox.onlinephpfunctions.com/code/…
Onkeltem
Darüber hinaus kann der Splat-Operator keine String-Schlüssel entpacken. Beweis des Fehlers: 3v4l.org/1WSQH ... whoops, ich habe gerade festgestellt, dass ich dies vor über einem Jahr als Antwort auf dieser Seite gesagt habe!
mickmackusa

Antworten:

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

Oder wenn Sie PHP 5.6 oder höher verwenden:

function transpose($array) {
    return array_map(null, ...$array);
}
Codler
quelle
2
Hm ... es funktioniert für mich, aber ich verstehe nicht wirklich warum. Ich sehe, dass array_map mit $ array als Parametern aufgerufen wird, während null der erste Parameter ist. Aber warum verhält sich array_map so? Warum ist null als Parameter hier überhaupt in Ordnung?
Jakob Runge
20
NULL wird als Parameter für array_unshift angegeben, wodurch am Anfang des Arrays ein Wert hinzugefügt wird. In der ersten Zeile wird also NULL als erster Wert des Arrays eingefügt. In der nächsten Zeile wird array_map mit allen Einträgen von $ array als Parametern aufgerufen. Es ist also dasselbe wie das Aufrufen von array_map (NULL, $ array [0], $ array [1], $ array [2] usw. usw.). In der Dokumentation zu array_map gibt es ein Detail: "Eine interessante Verwendung dieser Funktion besteht darin, ein Array von Arrays zu erstellen, das einfach ausgeführt werden kann, indem NULL als Name der Rückruffunktion verwendet wird"
Jeremy Warne,
14
Diese Funktion behält die Indizes nicht bei, wenn sie vom Typ String sind. Es gibt die transponierte Matrix mit numerischem Index zurück. Die Funktion flipDiagonally funktioniert in diesem Fall einwandfrei. Zur Vereinfachung sowieso upvote
luso
5
Dies bricht zusammen, wenn es nur eine Zeile gibt, z. B. transponieren ([[1,2]]). Erwartet: [[1], [2]] Tatsächlich: [1,2]
Chris
3
sollte eine Beschreibung in der Antwort sein.
Awlad Liton
69

Mit 2 Schleifen.

function flipDiagonally($arr) {
    $out = array();
    foreach ($arr as $key => $subarr) {
        foreach ($subarr as $subkey => $subvalue) {
            $out[$subkey][$key] = $subvalue;
        }
    }
    return $out;
}
OIS
quelle
17
Obwohl Codlers Antwort prägnanter ist, denke ich, dass dies tatsächlich der bessere Weg ist, weil es so viel klarer ist, was los ist. Wenn sich jemand mit Programmiererfahrung, aber keinem PHP-Guru die beiden Antworten ansehen würde, würde er sofort verstehen, was diese tut, müsste aber das Kleingedruckte der Dokumentation lesen, um der anderen zu folgen. +1
Hackartist
Im Gegenteil, ich habe festgestellt, dass dies mit nicht numerischen Tasten nicht so gut abschneidet. Beispiel $test = array(array('a'=>1, 'b'=>2,'c'=>3), array(4,5,6), array(7,8,9));: Es wird für jeden Wert ein Einzelelement-Array mit einem nicht numerischen Schlüssel erstellt. Mit bestimmten Zifferntasten (z. B. $test = array(array(4,5,6), array(11=>1, 12=>2, 13=>3), array(7,8,9));) macht es etwas seltsam. Obwohl dies auf jeden Fall funktionieren sollte , denke ich, dass wir eine bessere Lösung brauchen!
JohnK
@JohnK [0] [1] und [2] [1] werden zu [1] [0] und [1] [2]. Es dreht die Tasten. Ich habe Ihre Beispiele ausprobiert und es funktioniert genau wie beabsichtigt. Ich bin mir nicht sicher, was du erwartet hast.
OIS
Hat jemand die beiden Lösungen profiliert? Wie gut skaliert die Lösung des Codlers, wenn die Anzahl ($ arr) wirklich hoch ist?
Donquijote
1
@donquixote Codlers Lösung ist falsch. Schlecht. Es unterstützt keine assoziativen Arrays und schlägt im trivialen Randfall einer einzelnen Zeile / Spalte fehl: [[a, b, ..., z]]. Es muss abgewertet werden, um Menschen nicht zu verwirren.
Onkeltem
8

Ich denke, Sie beziehen sich auf die Array- Transponierung (Spalten werden zu Zeilen, Zeilen werden zu Spalten).

Hier ist eine Funktion, die dies für Sie erledigt (Quelle) :

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
quelle
3

Transponieren eines N-dimensionalen Arrays:

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

Wirklich hackisch und wahrscheinlich nicht die beste Lösung, aber hey, es funktioniert.

Grundsätzlich durchläuft es das Array rekursiv und akkumuliert die aktuellen Anzeigen in einem Array.
Sobald es den referenzierten Wert erreicht hat, nimmt es den "Stapel" von Indizes und kehrt ihn um und fügt ihn in das $ out-Array ein. (Gibt es eine Möglichkeit, die Verwendung des $ temp-Arrays zu vermeiden?)

v3.
quelle
2

Ich brauchte eine Transponierungsfunktion mit Unterstützung für assoziatives 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],
    ];

Und der Rückweg:

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

Der array_unshiftTrick hat weder funktioniert noch array_map...

Also habe ich eine array_map_join_arrayFunktion für die Zuordnung von Datensatzschlüsseln codiert :

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

und array_transposewurde offensichtlich:

function array_transpose(array $matrix)
{
    return \array_map_join_array(function ($key, $items) {
        return $items;
    }, $matrix);
}
quazardous
quelle
1

Ich wurde mit dem gleichen Problem konfrontiert. Folgendes habe ich mir ausgedacht:

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;
}
José Trindade
quelle
Dieser Antwort fehlt die einfache englische Erklärung.
Mickmackusa
1

Wenn Sie versuchen, die Beispieldaten des OP mit dem splat-Operator ( ...) zu entpacken , generieren Sie:

Schwerwiegender Fehler: Nicht erfasst Fehler: Array kann nicht mit Zeichenfolgenschlüsseln entpackt werden

Beweis

Um diesen Fehler array_values()zu beheben, rufen Sie auf, um die Schlüssel der ersten Ebene vor dem Entpacken zu indizieren.

var_export(array_map(null, ...array_values($foo)));

Ausgabe:

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

Ein zusätzliches Merkmal / eine Überraschung beim Transponieren mit dieser Technik ist, dass nullElemente generiert werden, wenn die Subarrays unterschiedliche Größen haben ... aber möglicherweise nicht dort, wo Sie es erwarten.

Aus Beispieldaten wie diesen:

$foo = array(
    'a' => array(
       1 => 'a1',
       2 => 'a2'
    ),
    'b' => array(
       1 => 'b1',
       3 => 'b3' 
    ),
    'c' => array(
       1 => 'c1',
       2 => 'c2',
       3 => 'c3' 
    )
);

Die Ausgabe ist:

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

Beachten Sie die Sorgfalt der Funktion (vergleichbar mit den Gepäckabfertigern, die Ihr Gepäck aus dem Bauch des Flugzeugs nehmen). Es gibt keine Aufmerksamkeit auf die ursprünglichen Werte Subarray Ide (und es würde keine Rolle , ob 1, 2u 3waren x, yu z); Was auch immer vom Förderband kommt, wird in den niedrigsten verfügbaren Schlitz geworfen.

Dieses Verhalten ist konsistent und zuverlässig bei der Bereitstellung einer vollständigen Matrix. Eine foreach()Schleifenalternative liefert nicht nativ nullElemente aus Subarrays unterschiedlicher Größe, und in den meisten Implementierungen hängt ihre Fähigkeit, auf alle Subarray-Werte zuzugreifen, von der Länge des ersten Subarrays ab.

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

Ausgabe:

array (
  0 => 
  array (
    0 => 'a1',
    1 => 'b1',
    2 => 'c1',
  ),
  1 => 
  array (
    0 => 'a2',
    1 => 'c2',
  ),
)

Wie oben gezeigt, müssen Sie, wenn Sie sicherstellen möchten, dass Sie ALLE Daten aus dem Eingabearray extrahiert haben, eine Additionslogik schreiben, um alle eindeutigen Spalten-IDs an die foreach-Schleife zu senden.


ps Bevor ich von dieser Kurztransponierungssyntax erfuhr, schrieb ich einen hässlicheren, ausführlicheren funktionalen Transposer, der einige Kritik auf sich zog .

mickmackusa
quelle
1

Hier ist eine Variation der Codler / Andreas-Lösung , die mit assoziativen Arrays funktioniert. Etwas länger aberschleifenlos rein funktional:

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

Beispiel:

<?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
    )
);
Tomkyle
quelle
Dies ist nicht "loopless", es ist nur "funktional", mit anderen Worten, es iteriert ohne Sprachkonstrukte.
Mickmackusa
1

Wir können dies tun, indem wir Two foreach verwenden. Reisen ein Array und ein weiteres Array neues Array erstellen
wie folgt aus :

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

Überprüfen Sie die Ausgabe

echo "<pre>";
print_r($newFoo);
echo "</pre>";
Mehedi Hasan
quelle
0

Bevor ich beginne, würde Ich mag sagen , Dank wieder @quazardus für seine verallgemeinerte Lösung für tranposing zwei beliebige dimenional assoziativ (oder nicht assoziative) Arrays veröffentlichen!

Da ich es mir zur Gewohnheit gemacht habe, meinen Code so knapp wie möglich zu schreiben, habe ich seinen Code ein wenig weiter "minimiert". Dies wird sehr wahrscheinlich nicht jedermanns Geschmack sein. Aber nur für den Fall, dass jemand interessiert sein sollte, hier ist meine Sicht auf seine Lösung:

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

Jetzt analog zur PHP-Standardfunktion array_map(), wenn Sie aufrufen

arrayMap(null,$b);

Sie erhalten die gewünschte transponierte Matrix.

Autos10m
quelle
Es gibt sicherlich direktere, präzisere und effizientere Möglichkeiten, die eindeutigen Schlüssel der zweiten Ebene zu erfassen. Zum Beispiel: $keys = array_keys(array_merge(...array_values($arrays)));Ein Sprachkonstrukt hat eine geringere Komplexität und eine noch bessere Leistung.
Mickmackusa
0

Dies ist eine weitere Möglichkeit, genau das Gleiche zu tun, was die Antwort von @codler tut. Ich musste einige Arrays in csv sichern, damit ich die folgende Funktion verwendete:

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- und Ausgabereferenz: https://tutes.in/how-to-transpose-an-array-in-php-with-irregular-subarray-size/

th3pirat3
quelle
0

Hier ist array_walk Weg, um dies zu erreichen:

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
quelle
-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);

Versuchen Sie es so

MrCharif
quelle
1
Dies sieht aus wie eine ungeklärte exakte Nachbildung der @ OIS-Lösung. Diese Antwort enthält keinen Mehrwert.
Mickmackusa