I am seeking constructive feedback/advice that will give me the best
chance of a successful RFC when I eventually submit it.
Introduction:
PHP developers have had an appetite for transposing data for many
years; this is indicated by the nearly 100 Stack Overflow pages on
this narrow topic.
There are many non-native techniques being used to transpose
multidimensional data sets, but each of them are some combination of
verbose, convoluted, restrictive, and/or even flawed.
Transposing comes in handy when re-orientating data for server-side
storage or graphical/tabular display.
Ideally, PHP should have a native transposing function to put
developers out of their misery -- much like array_key_last()
did.
Proposal:
Let's create an intuitive transposing function for data sets with a
minimum depth of 2 levels.
The function should also return the input parameter without complaint
when the input parameter is an empty array/object.
The previous two points are purposely stated so that this function can
accommodate fetch-all result sets from database queries (e.g.
array_transpose($stmt->fetchAll(PDO::FETCH_ASSOC));).
As a matter of flexibility, there should be no requirement that the
lone parameter be a matrix or dense data structure.
It should also preserve keys to give it a clear advantage over
pre-existing functional techniques.
The function must never receive or return a non-empty one-dimensional array.
A potential consideration is to preserve data types while transposing
-- see implementation #2 and its demo link with test data.
Background:
Here are some of the popular techniques that this new function would replace:
array_unshift($array, null);
return call_user_func_array('array_map', $array);
// outdated, unintuitive, unreliable, requires non-numeric
first-level keys, destroys associative keys
// [['single' => 'row']] becomes ['single' => 'row'] but should be
['single' => ['row']]
return array_map(null, ...$array);
// unintuitive, unreliable, requires non-numeric first-level keys,
destroys associative keys
// [['single' => 'row']] becomes ['single' => 'row'] but should be
['single' => ['row']]
$out = [];
foreach ($arr as $key => $subarr) {
foreach ($subarr as $subkey => $subvalue) {
$out[$subkey][$key] = $subvalue;
}
}
return $out;
// reliable, but verbose and not functional-style
$keys = array_keys($array);
return array_map(function($array) use ($keys) {
return array_combine($keys, $array);
}, array_map(null, ...array_values($array)));
// convoluted, unintuitive, requires input to be a matrix
// [['single' => 'row']] generates Warning: `array_combine()`
expects parameter 2 to be array, string given"
Considerations:
Should the function unconditionally return an array of arrays?
Should it preserve the initial data types of the first and second level?
Should data type preservation be excluded to increase the chances of
its implementation?
Rough implementations of behavior:
1. Unconditionally cast output as array of arrays.
function array_transpose(array|object $object_or_array): array|object {
$levelOneType = gettype($object_or_array);
if (!in_array($levelOneType, ['array', 'object'])) {
throw new Exception("Error: array_transpose() expects
parameter 1 to be an array or object, $levelOneType given");
}
$result = [];
foreach ($object_or_array as $rowKey => $row) {
$levelTwoType = gettype($row);
if (!in_array($levelTwoType, ['array', 'object'])) {
throw new Exception("Error: array_transpose()
expects parameter 1 to contain rows of arrays or objects,
$levelTwoType given");
}
foreach ($row as $columnKey => $value) {
$result[$columnKey][$rowKey] = $value;
}
}
return $result;
}
2. Preserve original data types of level one and level two.
(If input is two dimensional, then the first occurring data type in
the second level will dictate the result's first level data type.)
function array_transpose(array|object $object_or_array): array|object {
$levelOneType = gettype($object_or_array);
if (!in_array($levelOneType, ['array', 'object'])) {
throw new Exception("Fatal error: Uncaught TypeError:
array_transpose(): Argument #1 ($object_or_array) must be of type
object|array, $levelOneType given");
}
foreach ($object_or_array as $rowKey => $row) {
$levelTwoType = gettype($row);
if (!in_array($levelTwoType, ['array', 'object'])) {
throw new Exception("Fatal error: Uncaught
TypeError: array_transpose(): Argument #1 ($object_or_array) must
contain rows of type object|array, $levelTwoType given");
}
$result ??= ($levelTwoType === 'array' ? [] : (object)[]);
foreach ($row as $columnKey => $value) {
if ($levelTwoType === 'array') {
if ($levelOneType === 'array') {
$result[$columnKey][$rowKey] = $value;
} else {
$result[$columnKey]->$rowKey = $value;
}
} else {
if (!property_exists($result, $columnKey)) {
$result->$columnKey = ($levelOneType ===
'array' ? [] : (object)[]);
}
if ($levelOneType === 'array') {
$result->{$columnKey}[$rowKey] = $value;
} else {
$result->{$columnKey}->$rowKey = $value;
}
}
}
}
return $result ?? ($levelOneType === 'array' ? [] : (object)[]);
}
RFC Impact:
I can't see any backward incompatible concerns, but it is possible
that this new native function may impact codes where the function name
is already in use. (e.g.
https://github.com/josefernandotrindade/array_transpose/blob/master/transpose.php)
Pre-existing projects may have different rules relating to the
specific handling of data types and data structures.
No new constants.
No php.ini defaults.
Future Scope:
If only initially implemented to return an array of arrays, then
expanding the result data types in the future may be of interest.
List of Stack Overflow pages which seek to implement transposition
(approaching 100K page views in the aggregate): link list @
https://meta.stackoverflow.com/q/417663/2943403
2009 Transposing multidimensional arrays in PHP -- 50K views
2009 Joining Arrays in PHP -- 150 views
2010 Is there better way to transpose a PHP 2D array? -- 5K views
2010 Combine 3 or more arrays (php) -- 4K views
2010 Can I convert individual fields of a 'mysql_query' to arrays? -- 500 views
2011 Combine arrays in PHP 300 views
2011 PHP: Take several arrays, and make new ones based on shared
indexes? -- 100 views
2011 Insert Multiple rows using implode -- 3K views
2011 how convert the element structure of an array in php -- 100 views
2011 php reassign array contents -- 150 views
2012 PHP Grouping Array by Index -- 1K views
2012 displaying a mysql table vertically - for comparison or for print
out purposes -- 4K views
2012 Elegant way to convert associative array into an indexed one -- 3K views
2012 PHP Grouping Array by Index -- 1K views
2012 Order in loops foreach -- 350 views
2012 PHP Array, Move Elements -- 100 views
2013 Refactor (transpose) array to unique keys -- 200 views
2013 How to merge 3 arrays into one big array (same keys) -- 200 views
2013 Smarty/PHP - How to match up keys between 3 different arrays? -- 750 views
2013 PHP rotate matrix counter-clockwise -- 3K views
2013 loop to populate html table vertically -- 9K views
2013 How to merge 3 arrays into one big array (same keys) -- 200 views
2014 PHP looping array to get combined rows of items -- 50 views
2014 MYSQL / Query Builder / Eloquent - Transpose rows to columns -- 1k views
2014 Change several ARRAY into new ARRAYS -- 50 views
2014 array merge issue in php -- 50 views
2014 How to combine associative array by indexes -- 450 views
2014 PHP: Grouping array values by keys -- 150 views
2014 How to transpose two arrays to form an indexed array of
associative arrays? -- 100 views
2014 PHP - multidimensional array from arrays -- 100 views
2014 Display transposed data as html table -- 100 views
2015 I have 5 arrays each array contains 10 values, I want to invert
these arrays into 10 arrays and each array contains 5 values using
php? -- 6K views
2015 create transpose matrix using php -- 6K views
2015 Why does array_map()
with null as callback create an "array of
arrays"? -- 2K views
2015 Chunk and transpose a flat array into rows with a specific number
of columns -- 50 views
2015 Merge 2 arrays into 1 multidimensional array by index -- 800 views
2015 Merging two simple arrays into a multidimensional array -- 50 views
2015 Populating HTML table with multidimensional array vertically -- 1K views
2015 Merge multidimensional array preserving keys -- 100 views
2015 Transpose csv file data -- 300 views
2016 Multi dimensional loops from 4 existing arrays -- 50 views
2016 How to use an array of arrays with array_map(...) in PHP? -- 2K views
2016 How can I combine arrays in one multidimensional array in php? -- 50 views
2016 PHP array, move keys and values to new array, but mix it up -- 50 views
2016 How to transpose array elements? -- 900 views
2016 Hierarchical Array Conversion to Flat Array -- 300 views
2016 merge and reorder 3 arrays -- 50 views
2016 Insert database rows from columns of data from associative array
of indexed arrays -- 50 views
2017 Transpose rows and columns in a 2D array -- 5K views
2017 php reformat array representation from html form array -- 50 views
2017 How to insert arrays into a main array in PHP to make it 2d
array? -- 50 views
2017 How can I combine array's items? -- 250 views
2017 Restructure multidimensional array of column data into
multidimensional array of row data -- 1K views
2017 Transpose associative array of associative arrays to create
associative array of indexed arrays -- 4K views
2017 Merging PHP array to multidimensional array from another array -- 50 views
2018 Combine items of two array and create new array using php -- 50 views
2018 How to collect 3 arrays in 1 array using PHP? -- 50 views
2018 How do I use array_map recursively in PHP? -- 1K views
2018 Laravel loop through set of input arrays -- 150 views
2018 Foreach to combine to array -- 50 views
2019 php extract associate arrays -- 50 views
2019 How to combine and transpose 2 PHP arrays -- 50 views
2019 How to merge two arrays into one without iteration -- 100 views
2019 How to create an associative array from two arrays? -- 3K views
2019 how to get each column of values as array directly, and without
using pluck laravel? -- 650 views
2019 Combine (merge) 2 arrays by keys and change keys name in the
result array -- 800 views
2019 Array_map and array_combine for more than two arrays -- 500 views
2019 Merge values from different arrays to one with the same key -- 100 views
2019 Native PHP function that pairs elements from arbitrary number of
input arrays -- 50 views
2019 Transpose multidimensional array and join values with commas -- 50 views
2019 How to create 3 arrays from 1 multidimensional array with 2
arrays inside -- 50 views
2019 How to store 3 key by index per row PHP -- 50 views
2019 How to change associative array value in php -- 400 views
2019 How to put array inside array in PHP? -- 50 views
2019 How to Loop through multi dimensional array of $_FILES array -- 200 views
2019 How to change associative array value in php -- 400 views
2019 PHP multi-dimensional array, merge duplicate keys into new arrays
-- 50 views
2020 Transpose of column to row in php -- 50 views
2020 combine and merge array in laravel -- 100 views
2021 how to import a transposed excel in Laravel using laravel-excel
-- 450 views
2021 Transpose imported Excel with Matrix table to Laravel -- 50 views
2021 Group multi array by key and value PHP -- 50 views
2021 Create a multidimensional array based on number of values -- 50 views
2022 Rotate a Collection in laravel -- 50 views
2022 Zip multiple arrays in PHP based on their identical keys and not
their indices or their positions in array -- 50 views
2022 Convert nested associative array to single array in php -- 50 views
Stack Overflow transposition pages not in PHP:
What is the fastest way to transpose a matrix in C++?
Transpose/Unzip Function (inverse of zip)?
Transposing a 2D-array in JavaScript
An efficient way to transpose a file in Bash
Java: Multi-dimensional array transposing
mickmackusa
p.s. I've been told that I'll need a sprinkling of karma if I am going
to submit an RFC, so please shout me some of that.
p.s. I've been told that I'll need a sprinkling of karma if I am going
to submit an RFC, so please shout me some of that.
Did you already register as a new Wiki user[1]? If so, what's the username?
[1] https://wiki.php.net/?do=register
--
Christoph M. Becker
Ideally, PHP should have a native transposing function to put
developers out of their misery -- much likearray_key_last()
did.
I think a better comparison would be array_column -
https://wiki.php.net/rfc/array_column One interesting thing that Ben
Ramsey wrote alongside that RFC was a pure PHP implementation with
identical behaviour, available for use as a polyfill:
https://github.com/ramsey/array_column
Unlike array_column, I don't recall personally needing an
array_transpose function, but am willing to believe that many people do.
My first piece of advice would be to start with a definition of what you
mean by "transpose" in this context, with an example of input and output
in the basic case, and then a few scenarios where it's useful.
Having that clear would then help understand the details you go into,
each of which could be accompanied by its own example. For instance:
Let's create an intuitive transposing function for data sets with a
minimum depth of 2 levels.
I can't picture what "transpose" means for something with more than 2
levels.
As a matter of flexibility, there should be no requirement that the
lone parameter be a matrix or dense data structure.
I have no idea what this means.
It should also preserve keys to give it a clear advantage over
pre-existing functional techniques.
Preserve what keys?
// [['single' => 'row']] becomes ['single' => 'row'] but should be
['single' => ['row']]
Should it? Why?
My second piece of advice is to remember that flexibility, ease of use,
and efficiency, are three corners of a triangle, and you can't optimise
for all three. array_column does a reasonable job of covering multiple
use cases, but there have been several unsuccessful requests over the
years to enhance it or create variations for other use cases, so you're
never going to make everyone happy.
Should the function unconditionally return an array of arrays?
Should it preserve the initial data types of the first and second level?
Should data type preservation be excluded to increase the chances of
its implementation?
[...]
1. Unconditionally cast output as array of arrays.
[...]
2. Preserve original data types of level one and level two.
https://3v4l.org/RRKlr(If input is two dimensional, then the first occurring data type in
the second level will dictate the result's first level data type.)
[...]
If only initially implemented to return an array of arrays, then
expanding the result data types in the future may be of interest.
Without really understanding what you're saying, my suspicion is that
these paragraphs are trying too hard to make a Swiss-army knife rather
than a sharp blade.
List of Stack Overflow pages which seek to implement transposition
Would all of these requirements be satisfied by the same implementation,
without needing a complex set of options? What can we learn from the
links about what features are likely to be most useful to people?
I look forward to seeing a draft RFC, which can take the time to explain
the features you think are needed.
Regards,
--
Rowan Tommins
[IMSoP]
Ideally, PHP should have a native transposing function to put
developers out of their misery -- much likearray_key_last()
did.I think a better comparison would be array_column -
https://wiki.php.net/rfc/array_column One interesting thing that Ben
Ramsey wrote alongside that RFC was a pure PHP implementation with
identical behaviour, available for use as a polyfill:
https://github.com/ramsey/array_columnUnlike array_column, I don't recall personally needing an
array_transpose function, but am willing to believe that many people do.My first piece of advice would be to start with a definition of what you
mean by "transpose" in this context, with an example of input and output
in the basic case, and then a few scenarios where it's useful.Having that clear would then help understand the details you go into,
each of which could be accompanied by its own example. For instance:Let's create an intuitive transposing function for data sets with a
minimum depth of 2 levels.I can't picture what "transpose" means for something with more than 2
levels.As a matter of flexibility, there should be no requirement that the
lone parameter be a matrix or dense data structure.I have no idea what this means.
It should also preserve keys to give it a clear advantage over
pre-existing functional techniques.Preserve what keys?
// [['single' => 'row']] becomes ['single' => 'row'] but should be
['single' => ['row']]
Should it? Why?
My second piece of advice is to remember that flexibility, ease of use,
and efficiency, are three corners of a triangle, and you can't optimise
for all three. array_column does a reasonable job of covering multiple
use cases, but there have been several unsuccessful requests over the
years to enhance it or create variations for other use cases, so you're
never going to make everyone happy.Should the function unconditionally return an array of arrays?
Should it preserve the initial data types of the first and second level?
Should data type preservation be excluded to increase the chances of
its implementation?
[...]
1. Unconditionally cast output as array of arrays.
[...]
2. Preserve original data types of level one and level two.
https://3v4l.org/RRKlr(If input is two dimensional, then the first occurring data type in
the second level will dictate the result's first level data type.)
[...]
If only initially implemented to return an array of arrays, then
expanding the result data types in the future may be of interest.Without really understanding what you're saying, my suspicion is that
these paragraphs are trying too hard to make a Swiss-army knife rather
than a sharp blade.List of Stack Overflow pages which seek to implement transposition
Would all of these requirements be satisfied by the same implementation,
without needing a complex set of options? What can we learn from the
links about what features are likely to be most useful to people?I look forward to seeing a draft RFC, which can take the time to explain
the features you think are needed.Regards,
--
Rowan Tommins
[IMSoP]
Thanks for the feedback, Rowan. Here are some answers and thoughts...
The basic premise of transposition is to replace the first level keys
with the second level keys.
This can be visualized as converting columns of data into rows of data.
With:
A B C
D E F
G H I
the expected result is:
A D G
B E H
C F I
When dealing with an indexed array of indexed arrays with more than
one row, array_map(null, ...$array)
is a beautiful, concise
technique.
However, an array with a single row returns an unexpected structure,
and associative arrays and objects cause trouble.
Using nested foreach loops is reliable, but is less elegant, requires
multiple temporary variables, certainly more verbose, and must be
wrapped in a custom function for functional-style coding.
Like array_column()
's ability to respect object data, I felt it might
be beneficial to allow/respect objects with this function proposal.
I've made a judgment call to swap the data types of the first level
with the second level, but perhaps data types should be honored, yet
not swapped.
With my type-preserving implementation, an array of objects will
become an object of arrays.
Perhaps an array of objects should remain an array of objects after
transposition?
I'm open to feedback on this behavior.
If there is no support for any type preservation (swiss army knife)
and the function should unconditionally return an array of arrays,
then it will improve performance to use the "basic" implementation for
the RFC.
I don't program in C so I don't know if there are any optimizations
that can be enjoyed.
Indicated by Stack Overflow pages that I've found, array_transpose()
would be useful when re-orientating $_POST, $_FILES, csv data, curl
response data, or sql result set data.
As a simple example, "zip" three arrays together to form an indexed
array of associative arrays. https://3v4l.org/jOQte
$names = ['Anne', 'Burt', 'Chad', 'Dawn'];
$ages = [18, 22, 20, 21];
$pets = ['dog', 'cat', 'fish', 'bird'];
var_export(array_transpose(compact(['names', 'ages', 'pets'])));
Test cases comparing three transposition techniques:
https://3v4l.org/Ns8M7 Given an empty array, the input array should
be returned, "splat" breaks.
https://3v4l.org/MZv9c Given an empty object, "basic" will return an
empty array, "types" will return the input object, "splat" breaks.
https://3v4l.org/0h8kc Given a 2d array with a single row, a 2d array
should be returned, "splat" undesirably flattens to a 1d array.
https://3v4l.org/8dnKr Given an assoc (gapped/non-dense, not a
matrix) array of indexed arrays, an indexed array of associative
arrays should be returned, "splat" breaks.
https://3v4l.org/nob75 Given an indexed (matrix) array of assoc
arrays, an assoc array of indexed arrays should be returned, "splat"
does not preserve assoc first level keys.
https://3v4l.org/2lYYAj Given an indexed array of objects (has more
than 2 levels), "basic" returns an array of arrays, "types" returns an
object of arrays, "splat" breaks.
https://3v4l.org/peqOO Given an object containing an object and an
array, "basic" returns an array of arrays, "types" returns an object
or objects, "splat" breaks.
https://3v4l.org/Eh83G Given an array containing two empty arrays,
all return an empty array.
https://3v4l.org/IPXqr Given an array containing two empty arrays,
then a single-element array, "basic" and "types" remove the empty
arrays and transpose the third array, "splat" fills empty array
position with nulls.
https://3v4l.org/EE39i Given an array containing two arrays
containing an array each, all return an array containing an array
containing two arrays.
mickmackusa
Hey,
I find it interesting that you linked
https://github.com/josefernandotrindade/array_transpose/blob/master/transpose.php
Wouldn't it be better to take that, brush it up (with some tests and
benchmarks), and release it as a packagist library?
Alternatively, adding it to a high quality library such as
https://github.com/azjezz/psl
I personally don't see a reason to have this in core, let alone implemented
in C.
P.S.: sorry for dropping all the quoted messages, but ML just reported that
your message contained a lot of additional markup, and therefore my
previous message got rejected.
Marco Pivetta