4

I have an array of products like this:

$rows = [
    100 => [
        ['product_id' => 101, 'name' => ''],
        ['product_id' => 102, 'name' => ''],
        ['product_id' => 103, 'name' => ''],
    ],
    200 => [
        ['product_id' => 201, 'name' => ''],
        ['product_id' => 202, 'name' => ''],
    ],
    300 => [
        ['product_id' => 301, 'name' => ''],
        ['product_id' => 302, 'name' => ''],
        ['product_id' => 303, 'name' => ''],
        ['product_id' => 304, 'name' => ''],
    ]
];

I want to transpose it into this structure which removes one level of depth:

$rows = [
    ['product_id' => 101, 'name' => ''],  //1st from 100 subArray
    ['product_id' => 201, 'name' => ''],  //1st from 200 subArray
    ['product_id' => 301, 'name' => ''],  //1st from 300 subArray
    ['product_id' => 102, 'name' => ''],  //2nd from 100 subArray
    ['product_id' => 202, 'name' => ''],  //etc...
    ['product_id' => 302, 'name' => ''],
    ['product_id' => 103, 'name' => ''],
    ['product_id' => 303, 'name' => ''],
    ['product_id' => 304, 'name' => ''],
];

Right now I'm trying to do this with this code:

$max_store_products = max(array_map('count', $rows));
$sortedArray = array();

for($j = 0; $j < $max_store_products; $j++)
{
    foreach($rows as $store_id => $store_products)
    {
        $sp = $rows[$store_id][$j];  

        if(isset($sp))
        {
            $sortedArray[] = $rows[$store_id][$j]; 
        }  
        else
            unset($rows[$store_id]);

    }
}

But this does take very long and also doesn't give me the expected output I want.

Is there any simpler way to do this?

mickmackusa
  • 43,625
  • 12
  • 83
  • 136
Zia
  • 41
  • 1

4 Answers4

3

The One-liner

This solution doesn't rely on array values being unique and sortable. It will merge all of your arrays by always taking an element off of each array.

$result = array_filter(array_reduce(array_map(null, ...$rows), 'array_merge' ,[]));

var_dump($rows);

Explanation

Let's break it down:

$columns = array_map(null, ...$rows);

When you pass null as the first argument of array_map, it has the same behaviour as:

$columns = array_map(function (...$vals) {
    return $vals;
}, ...$rows);

This basically transposes $rows, so you get an array of arrays with the first, second, third, etc. elements from each part. Passing null instead of a callback might look weird, but it's actually not a hack as it's described in the docs.

$rawResult = array_reduce($columns, 'array_merge', []);

Here you simply merge all of the columns into a single array.

$result = array_filter($rawResult);

array_filter removes any NULL entries, in case your inner arrays weren't of even length.

Old solution: Merge first, sort after

Assuming your values have something to sort by, like product_id in your example, you can merge the arrays first and then sort the result:

$result = array_merge(...$rows);

usort($result, function ($a, $b) {
    return $a['product_id'] % 100 - $b['product_id'] % 100 ?: $a['product_id'] - $b['product_id'];
});

var_dump($result); // merged & sorted result
Community
  • 1
  • 1
Kuba Birecki
  • 2,926
  • 1
  • 13
  • 16
  • I think you didn't get my point in full. $result = array_merge(...$rows); is useful and I am very close to solve the issue.. – Zia May 04 '16 at 07:00
  • 1
    Alright, what did I miss then ? I thought you only wanted to merge the arrays and have the result sorted in a specific manner. – Kuba Birecki May 04 '16 at 07:37
  • I actually came up with a cool one liner that doesn't depend on the ability to sort the items. In case you find it interesting. – Kuba Birecki May 04 '16 at 13:41
  • @KubaBirecki Interesting one-liner solution with a good explanation! Also from the new answer style and vocabulary you used in the answer I guess you also read my answer ;) – Rizier123 May 04 '16 at 18:35
  • 1
    @Rizier123 It's not the first answer of yours that I read :) Some of them are quite an inspiration. I like your solution as well, it's what made me try to push it a little bit further. – Kuba Birecki May 04 '16 at 19:09
2

Explanation

Let's take a simple array for the explanation:

Array(
    0 => Array(1, 4, 7),
    1 => Array(2, 5, 8),
    2 => Array(3, 6, 9),
)

First we array_shift() an anonymous function into your array, so your array looks like this:

Array(
    0 => function(){...}

    //Your values
    1 => Array(1, 4, 7),
    2 => Array(2, 5, 8),
    3 => Array(3, 6, 9),
)

Then we call call_user_func_array() and apply array_map() as callback. What this is going to do is basically:

//↓ 'array_map()' callback from 'call_user_func_array()'
array_map(function(){
    //Callback from the First array element

//        Second          Third           Fourth
//     array element   array element   array element   more elements...
//          ↓               ↓               ↓           ↓
}, Array(1, 4, 7), Array(2, 5, 8), Array(3, 6, 9) /* , ... */);

Array(1, 4, 7), Array(2, 5, 8), Array(3, 6, 9) /* , ... */
      │  │  │         │  │  │         │  │  └─ 3 iteration in 'array_map()' | 3 value
      │  │  │         │  │  │         │  └──── 2 iteration in 'array_map()' | 3 value
      │  │  │         │  │  │         └─────── 1 iteration in 'array_map()' | 3 value
      │  │  │         │  │  │
      │  │  │         │  │  └───────────────── 3 iteration in 'array_map()' | 2 value
      │  │  │         │  └──────────────────── 2 iteration in 'array_map()' | 2 value
      │  │  │         └─────────────────────── 1 iteration in 'array_map()' | 2 value
      │  │  │
      │  │  └───────────────────────────────── 3 iteration in 'array_map()' | 1 value
      │  └──────────────────────────────────── 2 iteration in 'array_map()' | 1 value
      └─────────────────────────────────────── 1 iteration in 'array_map()' | 1 value

Now this means we are going to loop through all subArrays at once, value by value and apply that anonymous function to all values which we loop through. In the anonymous function itself we basically just array_merge() the values which we got in the current iteration to the result array:

$result = array_merge($result, Array(1, 2, 3));  //First values from each subArray
$result = array_merge($result, Array(4, 5, 6));  //Second values from each subArray
$result = array_merge($result, Array(7, 8, 9));  //Third values from each subArray
//...

Code

<?php

    $arr = []; //Your array here
    $result = [];
    array_unshift($arr, function(...$values)use(&$result){
        $result = array_merge($result, array_filter($values));
    });

    call_user_func_array("array_map", $arr);

    print_r($result);

?>
Rizier123
  • 58,877
  • 16
  • 101
  • 156
0

Fairly sure this is what you want:

<?php

$result = [];
// Get the highest number of items in an entry of `$rows`
$maxItems = 0;
foreach($rows as $row) {
   if (count($row)>$maxItems) $maxItems = $row;
}

for($ii=0; $ii < $maxItems; $ii++) {

   foreach($rows as $row) {

     if (isset($row[$ii])) {
       $result[] = $row[ii];
     }

   }

}

?>

The reason your own version is failing, is because you have this line:

if(isset($sp))

However, $sp is always defined there. PHP does not have an undefined type like javascript does, so once you take an item from an array (whether it exists or not), it's defined, throws a E_NOTICE and results in null. You should really crank up your error messaging in PHP as this would be obvious.

Now as for speed, there's not a ton you can do to improve this. You could:

  1. Remove the first loop to get the counts and combine it with the second loop, but I doubt that this will make a big difference unless the loop is actually massive.
  2. If memory is your main bottleneck, you could attempt to switch to an approach that uses generators. This will be a bit slower purely from a CPU point of view, but if your source data is absolutely massive and your PHP script is causing your server to swap to cope, then a streaming, generator based approach might give you better performance.

Regardless it might be better if you think about why you are doing this in the first place, and if there's saner ways to solve this problem without just fixing your loop. If you really get bad performance from this because you're juggling a ton of data, the application design is probably what needs fixing, not this loop.

Evert
  • 93,428
  • 18
  • 118
  • 189
-1

Use,

function array_flatten($array) { 
 if (!is_array($array)) { 
  return FALSE; 
 } 
 $result = array(); 
 foreach ($array as $key => $value) { 
  if (is_array($value)) { 
   $result = array_merge($result, array_flatten($value)); 
  } 
  else { 
   $result[$key] = $value; 
  } 
 } 
 return $result; 
} 
Mansoor H
  • 594
  • 1
  • 8
  • 25