Jump to content

Manipulating Multi dimensional array


Go to solution Solved by soyaslim,

Recommended Posts

Hello guys, How r we? I am trying to manipulate multi-dimension array to the desired array. So, I have this array $products that have three `elements`. Lets say the element as`product` so, three products.

$products = [
    [
        'test' => [
            'make' => 'test make',
            'vehicle_info' => [
                '123' => [
                    'brake' => 'brake 123 value'
                ],
                '234' => [
                    'brake' => 'brake 234 value'
                ]
            ]
        ],
        'test2' => [
            'vehicle_info' => [
                '456' => [
                    'brake' => 'brake 456 value'
                ],
                '678' => [
                    'brake' => 'brake 678 value'
                ]
            ]
        ]
    ],
    [
        'test' => [
            'make' => 'test make',
            'vehicle_info' => [
                '234' => [
                    'brake' => 'brake 234 value'
                ]
            ]
        ],
        'test2' => [
            'vehicle_info' => [
                '456' => [
                    'brake' => 'brake 456 value'
                ]
            ]
        ],
        'test3' => [
            'vehicle_info' => [
                '789' => [
                    'brake' => 'brake 789 value'
                ]
            ]
        ]
    ],
    [
        'test' => [
            'make' => 'test make',
            'vehicle_info' => [
                '234' => [
                    'brake' => 'brake 234 value'
                ]
            ]
        ],
        'test3' => [
            'vehicle_info' => [
                '789' => [
                    'brake' => 'brake 789 value'
                ]
            ]
        ]
    ]
];

So, here is the desired output of the common array between 3 products. Since, the key `test` is common in all three products and `234` is common within `test` array in all three products.
 

Array
(
    [test] => Array
        (
            [make] => test make
            [vehicle_info] => Array
                (
                    [234] => Array
                        (
                            [brake] => brake 234 value
                        )

                )

        )

)

So, the solution I have works fine but I want to optimize my code or to improve it as much as possible. Please let me know if the steps are not clear.
 

$commonArray = [];
$total = count($products) - 1;


$vehicles = $products[0];
foreach ($vehicles as $key => $vehicle) {
    foreach ($vehicle['vehicle_info'] as $ktype => $fitments) {
        $countKey = 0; // keep track of key exists in all products
        // loop through all the products
        foreach ($products as $index => $product) {
            if ($index == 0) // skip the first product
                continue;

            foreach ($product as $k => $pro) {
                if ($key == $k) {
                    foreach ($pro['vehicle_info'] as $kt => $fits) {
                        if ($ktype == $kt) {
                            $countKey++;
                            if ($countKey == $total) { // check if the key exists in all products
                                $commonArray[$key] = $pro;
                            }
                        }
                    }
                }
            }
        }
    }
}

print_r($commonArray);


 

Link to comment
https://forums.phpfreaks.com/topic/316211-manipulating-multi-dimensional-array/
Share on other sites

All you really need is some sort of "array_intersect_recursive", because the exact structure of the $products doesn't actually change how you compare the different parts of it to others, but unfortunately PHP doesn't have that built-in and implementing it yourself is a bit complicated.

A quick improvement is that you can array_shift($products) to remove the first one from the array and return it back to you. Means you don't need to skip $index 0 anymore.

A more complicated improvement would be to adjust your approach: rather than count how many times something appears before adding it, it would save some processing time if you immediately bailed out when you discovered that something did not appear. As in:

$commonArray = [];
$first = array_shift($products);
foreach ($first as $key => $vehicle) {
	$commonVehicle = [];

	// check if $key exists in all the other $products
	// if not, use continue (likely a continue 2; or continue 3;) to skip ahead of this loop here and go to the next $key
	// if so, build up the contents of $commonVehicle...

	if ($commonVehicle) {
		// only add this $key if there is something to add
		$commonArray[$key] = $vehicle;
	}
}

And like I said, while the keys of $products changes as you dig into it, you're repeating the same "check if <key> exists in other <arrays>" all the way down, so you'd basically just repeat the above code a couple more times with different variable names.

56 minutes ago, requinix said:

All you really need is some sort of "array_intersect_recursive", because the exact structure of the $products doesn't actually change how you compare the different parts of it to others, but unfortunately PHP doesn't have that built-in and implementing it yourself is a bit complicated.

A quick improvement is that you can array_shift($products) to remove the first one from the array and return it back to you. Means you don't need to skip $index 0 anymore.

A more complicated improvement would be to adjust your approach: rather than count how many times something appears before adding it, it would save some processing time if you immediately bailed out when you discovered that something did not appear. As in:

$commonArray = [];
$first = array_shift($products);
foreach ($first as $key => $vehicle) {
	$commonVehicle = [];

	// check if $key exists in all the other $products
	// if not, use continue (likely a continue 2; or continue 3;) to skip ahead of this loop here and go to the next $key
	// if so, build up the contents of $commonVehicle...

	if ($commonVehicle) {
		// only add this $key if there is something to add
		$commonArray[$key] = $vehicle;
	}
}

And like I said, while the keys of $products changes as you dig into it, you're repeating the same "check if <key> exists in other <arrays>" all the way down, so you'd basically just repeat the above code a couple more times with different variable names.

Hi @requinix Thanks for your feedback and suggestion and clean code. However, I need to check the vehicle info`key` which is `123`, `234` so on as well before adding to commonArray. I added the condition to your code like below. But it only check the `key` but not the vehicle info `key`.

$commonArray = [];
$first = array_shift($products);
foreach ($first as $key => $vehicle) {
	$commonVehicle = [];
    
    foreach ($products as $product) {
        foreach ($product as $k => $pro) {
            // check if $key exists in all the other $products
            if ($key == $k) {
                // if so, build up the contents of $commonVehicle...
                $commonVehicle[$key] = $pro;
            } else {
                // if not, use continue (likely a continue 2; or continue 3;) to skip ahead of this loop here and go to the next $key
                continue 2;
            }
        }
    }

	if ($commonVehicle) {
		// only add this $key if there is something to add
		$commonArray[$key] = $vehicle;
	}
}

print_r($commonArray);

 

I'll try to explain a little better about the approach I'm thinking of.

It's recursive. You start looking at the outermost values - the "test" keys in your example - and you decide whether they should be included in the common array. Should they? Only if they're present in the other arrays.
So you look at the other arrays. If they don't have a "test" then you already know the answer (no) and you can skip ahead to the next key. If so, you know that the key is present but you don't know if the corresponding value (the vehicle information) is the same; if it's partly the same, you just keep the common parts.
Next you look at that array's values - the "make" and "vehicle_info" keys - and you decide whether they should be included in this inner common array. Should they? Only if they're present in the other "test" sub-arrays.
So you look at the other sub-arrays. If they don't have the "make"/"vehicle_info"/whatever then you already know the answer (no) and you can skip ahead to the next sub-key. If so, you know that the key is present but you don't know if the corresponding value (the make, or vehicle info array) is the same; the make is a string so you can compare that, while if the vehicle info is partly the same, you just keep the common parts.

See how that's working? The same overall approach applies to the "test" parts, as well as the "make" and "vehicle_info" parts, as well as the actual vehicle parts themselves, as well as the data about the vehicle parts. At each of those different "layers", when you have an array to compare, you need to delve into that array to see what may be common with the other usages.

The main point to illustrate was the ability to stop early: if you're looking for some particular key, like the "make", then you naturally have to check the other arrays to see if they have it, but if they don't then you don't have to keep looking any further. And the technique for "stopping early" is that you (a) have this array of "common" parts you build up, as you confirm that each piece of data exists in the other arrays, and (b) at the end of whatever loop, you take that common array and add it to whatever the "parent" thing is (be that the big common array of data, or some sub-array of vehicles or parts or whatever), and you can use a continue as you're checking things to skip over that and immediately start checking the next key.

However,

Working through this now, I'm seeing that it's not as effective as I thought it might be, and that the code will be nicer if it took a slightly different approach: do the whole "stop early" thing, but instead of building up the common data, try tearing down the uncommon data.
There's also not so much a need to continue; anymore - as in, do something to skip past some code and restart a loop - because there's no dangling code. Could break; though, but I opted for using if/else blocks instead to make it easier to follow.

$vehicleGroups = [...];

$firstGroup = array_shift($vehicleGroups);
$commonGroup = $firstGroup;

foreach ($firstGroup as $id => $vehicle) {
	foreach ($vehicleGroups as $otherGroup) {
		// layer 1: the vehicle IDs

		// check if $id exists in all the other groups
		if (!isset($otherGroup[$id])) {
			// if not, tear down
			unset($commonGroup[$id]);
		} else {
			// if so, go deeper

			$otherVehicle = $otherGroup[$id]; // for convenience, to pair with $vehicle

			// layer 2: the make and vehicle_info

			// check if make exists and is in all the other groups
			if (isset($vehicle["make"])) {
				if (!isset($otherVehicle["make"])) {
					// if not, tear down
					unset($commonGroup[$id]["make"]);
				} else {
					// nothing to potentially go deeper into
				}
			}
			// same for vehicle_info
			if (isset($vehicle["vehicle_info"])) {
				if (!isset($otherVehicle["vehicle_info"])) {
					// if not, tear down
					unset($commonGroup[$id]["vehicle_info"]);
				} else {
					// layer 3: the vehicle_infos

					foreach ($vehicle["vehicle_info"] as $infoid => $info) {
						// check if $infoid exists in all the other groups
						if (!isset($otherVehicle["vehicle_info"][$infoid])) {
							// if not, tear down
							unset($commonGroup[$id]["vehicle_info"][$infoid]);
						} else {
							// if so, go deeper

							// layer 4: the bits of data in the vehicle_infos

							// I'm going to assume that "brake" is only one of many possible values
							// rather than handle each one like make/vehicle_info did individually, do another foreach

							foreach ($vehicle["vehicle_info"][$infoid] as $partid => $part) {
								// check if $partid exists in all the other groups
								if (!isset($otherVehicle["vehicle_info"][$infoid][$partid])) {
									// if not, tear down
									unset($commonGroup[$id]["vehicle_info"][$infoid][$partid]);
								} else {
									// nothing to potentially go deeper into...?
								}
							}
						}

						// don't keep the info if there was nothing in common
						if (!$commonGroup[$id]["vehicle_info"][$infoid]) {
							unset($commonGroup[$id]["vehicle_info"][$infoid]);
						}
					}
				}

				// don't keep vehicle_info if there was nothing in common
				if (!$commonGroup[$id]["vehicle_info"]) {
					unset($commonGroup[$id]["vehicle_info"]);
				}
			}

			// don't keep it if there was nothing in common
			if (!$commonGroup[$id]) {
				unset($commonGroup[$id]);
			}
		}
	}
}

print_r($commonGroup);

Note the similarities in the different "layers", and how they all repeat the same idea more or less the same way: if the thing is not present then remove, otherwise check it in more detail.

Is it better, or even shorter or simpler, than what you have? Not really. Could it be improved? Undoubtedly. But the point was to show the "you can stop early if you know it won't work" concept.

  • Solution

Hi @requinix , Thank you for your explanation and for taking the time to solve this problem. The whole recursive approach I really do not follow that method because I find it really confusing and hard, however, I am able to break it through my existing solution. Below I have simply break the loop if the `vehicle_info` `key` does not match 
 

$commonArray = [];
$total = count($products) - 1;

$vehicles = $products[0];
array_shift($products);

foreach ($vehicles as $key => $vehicle) {
    foreach ($vehicle['vehicle_info'] as $ktype => $fitments) {
        $countKey = 0;
        // check all the products
        foreach ($products as $index => $product) {
            if (!isset($product[$key]['vehicle_info'])) {
                // key not found in this product, move on to next key
                break 2;
            }

            foreach ($product[$key]['vehicle_info'] as $kt => $fits) {
                if ($ktype == $kt) {
                    $countKey++;
                    if ($countKey == $total) {
                        $commonArray[$key] = $product[$key];
                    }
                }
            }
        }
    }
}

print_r($commonArray);

 

Edited by soyaslim
This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.