1

For an array like this

$types = array(
    "text" => 2, 
    "image" => 2,
    "audio" => 2, 
);

How can I expand the data with a "round robin" order? In other words, I want to push array elements into the output array in successive waves.

Desired Output:

Array
(
    [0] => text
    [1] => image
    [2] => audio
    [3] => text
    [4] => image
    [5] => audio
)

Here is my current code:

$types = array(
    "text" => 2, 
    "image" => 2,
    "audio" => 2, 
);

$jump = count($types);
$output = [];

$j = 0;
foreach ($types as $item => $num) {
    for ($i=0; $i < $num; $i++) {
        $j = $j + $i + $jump;
        $output[$j] = $item;
    }
    $j + $jump;
}

print_r($output);
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
moimoi
  • 171
  • 1
  • 7

4 Answers4

1

I'm pretty sure there are numerous ways of doing this but one way would be to loop until you've covered all of the items inside the array.

You can use list() + each() combo in this case (so that you can reset the loop).

$data = []; $keys = array_keys($types); $last = end($keys);
while (list($type, $count) = @each($types)) {
    if ($count !== 0) { // push until count reaches zero
        $data[] = $type; // push it inside
        $types[$type]--; // subtract 1 count
    }
    
    if ($type === $last) { // if reached the end, reset the loop
        reset($types);
    }
    if (array_sum($types) === 0) { // end the loop if all are gone
        break;
    }
}

Sample output

Kevin
  • 41,694
  • 12
  • 53
  • 70
1

You're close with the code you have posted, you are just calculating the value of $j in the wrong way.

Using your own code, there are just 2 changes you need to make... See the comments below to see what needs to be changed:

$types = array(
    "text" => 2, 
    "image" => 2,
    "audio" => 2, 
);

$jump = count($types);
$output = [];

$j = 0;
foreach ($types as $item => $num) {
    for ($i=0; $i < $num; $i++) {
        $pos = $j + ($i * $jump);  // 1. multiple $i and $jump instead of adding them
        $output[$pos] = $item;
    }
    $j++;              // 2. increment $j by 1 instead of the value of $jump
}

print_r($output);

This outputs the following:

Array
(
    [0] => text
    [3] => text
    [1] => image
    [4] => image
    [2] => audio
    [5] => audio
)

As you can see, the array keys are correct, but they are added out of sequence. If you are iterating over the array with a numeric key (e.g. for ($i=0;$i<$size;$i++) ) this doesn't matter, but if you need the elements ordered by key, you can do this:

ksort($arr);

Result after sort:

Array
(
    [0] => text
    [1] => image
    [2] => audio
    [3] => text
    [4] => image
    [5] => audio
)

How this works:

You start with j as 0. Then for each "type" you add it into the array in position $j + ($i * $jump).

So for the first type, the position is calculated as 0+(0*3) i.e. 0 and then 0+(1*3)=3 i.e. 4th position which is after the first 3 elements from the $types list.

Now you move on and increment $j++ i.e. $j=1, and then loop does same thing except one position higher. (1+(0*3)=1 and 1+(1*3)=4).

When $j is 2, it does the same except one position higher again etc.

(Thanks to Hepson for spotting the earlier bug!)

FluffyKitten
  • 13,824
  • 10
  • 39
  • 52
  • $j isn't incremented by 1 on the second iteration of the foreach loop. The value is equal to 4. To correct it, use an other variable like `$x = $j + ($i * $jump); $output[$x] = $item;`. After that we need to reorganize the array according to the keys to match the desired order `ksort($output);`. Finally, if the value of each element isn't the same in $types, we might need to redo the keys `array_values($output);`. – Hepson Jul 29 '20 at 04:14
  • @Hepson Oops! Well spotted, thanks for letting me know! I just wrote that without testing it so I knew the logic I wanted to calculate the position but hadn't thought about overwriting the `$j` variable. Good tip on the sort also - I presume the array would be iterated using a numeric key so it wouldn't matter but I've added your tip just in case! – FluffyKitten Jul 29 '20 at 04:39
1

This feels pretty straight forward...

Use a while loop to keep cycling over the array until all elements are "spent". Use an inner loop to push elements into the result array then conditionally reduce the counter or unset the element.

Code: (Demo)

$result = [];
while ($types) {
    foreach ($types as $key => $value) {
        $result[] = $key;
        if ($value == 1) {
            unset($types[$key]);
        } else {
            --$types[$key];
        }
    }
}
var_export($result);

You can use a single loop, but it involves far more iterated function calls and I find it to be a little messier.

Code: (Demo)

$result = [];
while ($types) {
    $key = key($types);
    $result[] = $key;
    if ($types[$key] === 1) {
        unset($types[$key]);
    } else {
        --$types[$key];
    }
    if (!next($types)) {
        reset($types);
    }
}
var_export($result);

The above snippets assume you will not be populating the input array with 0 values.


If all of the count values are always identical, you could simplify the solution to push all of the keys into the result array using N number of passes. (Demo)

$result = [];
for ($i = 0, $universalCount = current($types); $i  < $universalCount; ++$i) {
    array_push($result, ...array_keys($types));
}
var_export($result);

p.s. Though contrary to the requirements, researchers landing on this page may be interested in simply expanding the values on a per-value basis: (Demo)

$result = [];
foreach ($types as $type => $count) {
    array_push($result, ...array_fill(0, $count, $type));
}
var_export($result);

Output:

array (
  0 => 'text',
  1 => 'text',
  2 => 'image',
  3 => 'image',
  4 => 'audio',
  5 => 'audio',
)
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
1

Another way would be this:

$types = array(
    "text" => 2, 
    "image" => 2,
    "audio" => 2, 
);

$output = [];

foreach ($types as $item => $num) {
    for ($i = 0; $i < $num; ++$i) {
        $output[$i][] = $item;
    }
}

$output = array_merge(...$output);

print_r($output);

https://3v4l.org/cL20Z

After the foreach the value of $output is:

Array
(
    [0] => Array
        (
            [0] => text
            [1] => image
            [2] => audio
        )

    [1] => Array
        (
            [0] => text
            [1] => image
            [2] => audio
        )

)

And this is flattened with array_merge.

gharlan
  • 96
  • 5