Home > Net >  Split array into multidimensional array at specific keys in PHP
Split array into multidimensional array at specific keys in PHP

Time:08-26

I have an array like this

array("split","cat", "split", "dog", "cow");

how can get a multi dimensional array at with split removed

like this

array( array( "cat"), array("dog", "cow") );

order should be preserved

I tried finding all the indexes where "split" happens then slice it but doesn't seems to work.

array("goat","split","cat", "split", "dog", "cow"); this is also possible

CodePudding user response:

Basically we keep reading the array, aggregating to a "bucket" then when encountering a "split" we dump the bucket into the final array result. And we won't forget to push the final bucket when exiting the loop.

$arr = array("split", "cat", "split", "dog", "cow");
$bucket = [];
$result = [];
while (count($arr)) {
    $item = array_shift($arr);
    if ($item === "split") {
        $result[] = $bucket;
        $bucket = [];
    } else {
        $bucket[] = $item;
    }
}
$result[] = $bucket;
print_r($result);

Output:

Array
(
    [0] => Array
        (
        )

    [1] => Array
        (
            [0] => cat
        )

    [2] => Array
        (
            [0] => dog
            [1] => cow
        )

)

CodePudding user response:

There are a number of ways to skin this cat.

To implement your first idea of finding the indices of the split tokens and slicing the array based upon those positions, you would do this:

$input = ["split", "cat", "split", "dog", "cow"];

// Get the indices of the split token
$splitIndices = array_keys(array_intersect($input, ['split']));

// Create an output buffer
$output = [];

// Initialize the last index var to zero
$lastIndex = 0;

// Iterate through the split indices
foreach($splitIndices as $currIndex)
{
    // If we have a splat in the first position, there is nothing to do
    if($currIndex == 0)
    {
        continue;
    }
    
    // Determine the number of elements to extract for this range
    $length = $currIndex-($lastIndex 1);
    
    // Slice out the range of elements
    $output[] = array_slice($input, $lastIndex 1, $length);
    
    // Update the last index
    $lastIndex = $currIndex;
}

// If the final element was not a split token, we need to add the remainder of the array
if(sizeof($input) > $lastIndex 1)
{
    $output[] = array_slice($input, $lastIndex 1);
}

print_r($output);

That's a bit of a faff, and error-prone. Let's try the classic approach of iterating through the array and adding the chunks to a buffer when we hit a token.

$input = ["split", "cat", "split", "dog", "cow"];
$output = [];

// Iterate through the array, send the current index into the loop along with the value.
foreach($input as $i=>$currValue)
{
    // If the current value is our delimiter...
    if($currValue == 'split')
    {
        // If we have items in the buffer, append it to the output
        if(!empty($currBuffer))
        {
            $output[] = $currBuffer;
        }
        
        // Clear the buffer and move to the next iteration
        $currBuffer = [];
        continue;
    }
    
    // Add the value to our buffer
    $currBuffer[] = $currValue;
    
    // If this is the last item, we need to set our buffer in the output array
    if($i == array_key_last($input))
    {
        $output[] = $currBuffer;
    }
}

print_r($output);

Hmmm. That's better, but having to keep track of the index is a bit smelly.

$input = ["split", "cat", "split", "dog", "cow"];

$output = [];
while(($currValue = array_shift($input)))
{
    if($currValue == 'split')
    {
        // If we have items in the buffer, append it to the output
        if(!empty($currBuffer))
        {
            $output[] = $currBuffer;
        }
        
        // Clear the buffer and move to the next iteration
        $currBuffer = [];
        continue;
    }
    
    // Add the value to our buffer
    $currBuffer[] = $currValue;
    
    // If this is the last item, we need to set our buffer in the output array
    if(empty($input))
    {
        $output[] = $currBuffer;
    }
}

print_r($output);

That's a bit better, no need to track the index, but it's still a lot of code to accomplish a simple task. We can do better.

$input = ["split", "cat", "split", "dog", "cow"];

$output = array_reduce($input, function($output, $item)
{
    if($item == 'split')
    {
        $output[] = [];
    }
    else
    {
        $output[array_key_last($output)][] = $item;
    }
    
    return $output;
});

print_r($output);

array_reduce - an elegant weapon, for a more civilized age.

CodePudding user response:

If you'd like your exact expected output (starting from index 0 of your array and going out from there) you can do something like this:

$arr = ["split", "cat", "split", "dog", "cow"];
$rebuild = [];
$offset = 0;

foreach ($arr as $key => $value) {
    if ($value === 'split') {
        $rebuild = array_filter([...$rebuild, array_slice($arr, $offset, $key - $offset)]);
        $offset =   $key;
    }
}

var_dump([...$rebuild, array_slice($arr, $offset, count($arr) - $offset)]);

Output:

array(2) {
  [0] => array(1) {
    [0] => string(3) "cat"
  },
  [1] => array(2) {
    [0] => string(3) "dog"
    [1] => string(3) "cow"
  }
}

See it working over at 3v4l.org


References:

  • Related