15

I have an array like this:

$arr = array(1, 1, 1, 2, 2, 3, 3, 1, 1, 2, 2, 3);

I found the function array_count_values(), but it will group all of the same values and count the occurrences without respecting breaks in the consecutive sequences.

$result[1] = 5
$result[2] = 4
$result[3] = 3

How can I group each set of consecutive values and count the length of each sequence? Notice there are two sets of sequences for the numbers 1, 2, and 3.

The data that I expect to generate needs to resemble this:

[1] = 3;
[2] = 2;
[3] = 2;
[1] = 2;
[2] = 2;
[3] = 1;
1
  • 1
    This is called run-length encoding. Commented Sep 13, 2021 at 16:49

4 Answers 4

16

It can be done simply manually:

$arr = array(1,1,1,2,2,3,3,1,1,2,2,3);

$result = array();
$prev_value = array('value' => null, 'amount' => null);

foreach ($arr as $val) {
    if ($prev_value['value'] != $val) {
        unset($prev_value);
        $prev_value = array('value' => $val, 'amount' => 0);
        $result[] =& $prev_value;
    }

    $prev_value['amount']++;
}

var_dump($result);
Sign up to request clarification or add additional context in comments.

Comments

4

My suggestion is to extract&remove the first value from the array prior to entering the loop and use a temporary array ($carry) to track whether each new value matches the key in the carry array. If so, increment it. If not, push the completed sequence count into the result array and overwrite the carry with the new value and set the counter to 1. When the loop finishes, push the lingering carry into the result set. My snippet does not check if the input array is empty; if necessary, add that condition to your project.

Code: (Demo)

$array = [1,1,1,2,2,3,3,1,1,2,2,3];
 
$result = [];
$carry = [array_shift($array) => 1];
 
foreach ($array as $value) {
    if (isset($carry[$value])) {
        ++$carry[$value];
    } else {
        $result[] = $carry;
        $carry = [$value => 1];
    }
}
$result[] = $carry;
print_r($result);

Output: (condensed to reduce page bloat)

[
    [1 => 3],
    [2 => 2],
    [3 => 2],
    [1 => 2],
    [2 => 2],
    [3 => 1],
]

If you'd rather implement a zerkms-style, modify-by-reference style technique, the following snippet provides the same result as the above snippet.

Effectively, it pushes every newly encountered value as an associative, single-element array into the indexed result array. Because the pushed subarray is declared as a variable ($carry) then assigned-by-reference (=&) to the result array, incrementation of $carry will be applied to the deeply nested value in the result array. The output array requires the additional depth in its structure so that a given value which occurs multiple times can be reliably stored.

Code: (Demo)

$result = [];
$carry = [];
foreach ($array as $value) {
    if ($carry && key($carry) === $value) {
        ++$carry[$value];
    } else {
        unset($carry);
        $carry = [$value => 1];
        $result[] =& $carry;
    }
}
unset($carry);
print_r($result);

Unsetting the reference variable $carry after the loop may not be necessary, but if there is any potential re-use of that variable within the variable's scope, it will be important to uncouple the reference with unset().

And just for fun, here is a hideous regex-infused approach that works with the sample data: Demo

var_export(
    json_decode(
        preg_replace_callback(
            '/\b(\d+)(?:,\1)*\b/',
            fn($m) => sprintf('{"%d":%d}', $m[1], count(explode(',', $m[0]))),
            json_encode($array)
        ),
        true
    )
);

Comments

3

What about PHP's array_count_values function?

<?php
$array = array(1, "hello", 1, "world", "hello");
print_r(array_count_values($array));
?>

output:

Array
(
    [1] => 2
    [hello] => 2
    [world] => 1
)

2 Comments

I have no idea why so many people have upvoted this incorrect answer. To spin my phrasing, this is the correct answer to a different question.
The problem with this answer is that it ignores the consecutive requirement from the question. This counts all values, regardless of position. When you run this function with the array from the question you do not get the desired result
-2
$current = null;
foreach($your_array as $v) {
    if($v == $current) {
        $result[count($result)-1]++;
    } else {
        $result[] = 1;
        $current = $v;
    }
}

var_dump($result);

2 Comments

a one liner for your solution would be $result = explode( ',' , implode(',', array_count_values($your_array) ) );
This answer loses the relationship between the number repeated in a given sequence and the length of the sequence. Amber, please improve this code only answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.