0

As part of a larger project, I'm receiving arrays in a form similar to this:

$out = array(
        '2011' => ['Out', 'arrv'],
        '2012' => ['Out'],
        '2013' => ['Out'],
        '2014' => ['Out'],
        '2015' => ['Out'],
        '2016' => ['some', 'none', 'test'],
        '2017' => ['Out'],
        '2018' => ['Out'],
        '2019' => ['Out'],
        '2020' => ['Out', 'did'],
        '2021' => ['Out'],
        '2022' => ['Out'],
        '2023' => ['Out', 'did']
);

I need to remove the consecutive "Out" lines, but keep the first and the last. My failed attempt:

foreach ($out as $dtp=>$dto) {
    if (count($dto) == 1) {
        if (str_contains($dto[0], "Out")) {
            $idx = array_search($dtp, array_keys($out));
            echo "$dtp contains the string; index is $idx" . PHP_EOL;
            var_dump($out[intval(array_keys($idx - 1))]);
            if ((str_contains($out[$idx-1][0], "Out")) && (str_contains($out[$idx+1][0], "Out")) && count($out[$idx-1] == 1) &&  count($out[$idx+1] == 1)) {
                array_splice($out, $idx, 1);
            }
        }
    }
}

Counts are there since there may be multiple events per timestamp. Echo returns the correct index number, but var_dump always returns NULL.

Current debugging output:

2017 contains the string; index is 6
PHP Warning:  array_keys() expects parameter 1 to be array, integer given in /home/redacted/pub/dbg2.php on line 28
PHP Notice:  Undefined offset: 0 in /home/redacted/pub/dbg2.php on line 28
NULL
2018 contains the string; index is 7
PHP Warning:  array_keys() expects parameter 1 to be array, integer given in /home/redacted/pub/dbg2.php on line 28
PHP Notice:  Undefined offset: 0 in /home/redacted/pub/dbg2.php on line 28
NULL
2019 contains the string; index is 8
PHP Warning:  array_keys() expects parameter 1 to be array, integer given in /home/redacted/pub/dbg2.php on line 28
PHP Notice:  Undefined offset: 0 in /home/redacted/pub/dbg2.php on line 28
NULL
2021 contains the string; index is 10
PHP Warning:  array_keys() expects parameter 1 to be array, integer given in /home/redacted/pub/dbg2.php on line 28
PHP Notice:  Undefined offset: 0 in /home/redacted/pub/dbg2.php on line 28
NULL
2022 contains the string; index is 11
PHP Warning:  array_keys() expects parameter 1 to be array, integer given in /home/redacted/pub/dbg2.php on line 28
PHP Notice:  Undefined offset: 0 in /home/redacted/pub/dbg2.php on line 28
NULL

While I haven't shared the full source code, the output from the sample array would be this:

[
    '2011' => ['Out', 'arrv'],
    '2012' => ['Out'],
    '2015' => ['Out'],
    '2016' => ['some', 'none', 'test'],
    '2017' => ['Out'],
    '2019' => ['Out'],
    '2020' => ['Out', 'did'],
    '2021' => ['Out'],
    '2022' => ['Out'],
    '2023' => ['Out', 'did']
]
5
  • So you also want to flatten the array? That's definitely worth pointing out in your question Commented Oct 8, 2024 at 0:34
  • @Phil I'd like to keep the keys, but if consecutive keys (by index) have only one value, and the value contains the string "Out", then they're deleted. Commented Oct 8, 2024 at 0:38
  • 3
    That expected output doesn't make any sense; you cannot have repeated keys in an array. Please format it as an actual PHP array. Commented Oct 8, 2024 at 0:40
  • @Phil You are right, I was/am too exhausted to notice that. Of course that's not the original code, but I failed in simplification. Be right at it. Commented Oct 8, 2024 at 0:52
  • @byte The break down of your coding intention is when you try to access $out elements using $idx -- but $out has no indexes, only year values as keys. Checking the previous, current, and next element values by $idx will prove to be very difficult for you. Even worse, once you start successfully splicing out unwanted elements, you'll need to make sure that subsequent attempts to find previous and next elements are finding the correct element. Commented Oct 8, 2024 at 9:27

3 Answers 3

1

Please note that your sample output array has duplicated keys, which I think, is not allowed. Try this:

<?php

$out = [
    '2011' => ['Out', 'arrv'],
    '2012' => ['Out'],
    '2013' => ['Out'],
    '2014' => ['Out'],
    '2015' => ['Out'],
    '2016' => ['some', 'none', 'test'],
    '2017' => ['Out'],
    '2018' => ['Out'],
    '2019' => ['Out'],
    '2020' => ['Out', 'did'],
    '2021' => ['Out'],
    '2022' => ['Out'],
    '2023' => ['Out', 'did']
];

$cleaned_out = [];
$first = true;
$last_key = null;
$last_value = null;
$key = null;
$value = null;
$printed_last_key = false;

foreach ($out as $key => $values) {
    foreach ($values as $value) {
        if ($first) {
            $last_key = $key;
            if ($value == 'Out') {
                $first = false;
            }
            echo $key . ' ' . $value . PHP_EOL;
            $printed_last_key = true;
        } else {
            if ($value == 'Out') {
                $last_key = $key;
                $last_value = $value;
                $printed_last_key = false;
            } else {
                if (!$printed_last_key) {
                    echo $last_key . ' ' . $last_value . PHP_EOL;
                }
                echo $key . ' ' . $value . PHP_EOL;
                $cleaned_out[$last_key] = $last_value;
                $cleaned_out[$key] = $value;
                $first = true;
                $printed_last_key = true;
            }
        }
    }
}

?>
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, but that's the point - it has duplicated keys (and subarrays), I just need to remove consecutive keys with the same value. I won't vote, this may help people from Google looking for something like this.
I think I fixed it. Please try it now
1

As you iterate over this associative array of rows, maintain a counter to allow you to check if the previous, current, and next element is explicitly ['Out'].

If so, then print nothing. Otherwise, print each value in the row with the first level key.

Code: (Demo)

$i = -1;
foreach ($out as $k => $row) {
    ++$i;
    if (array_slice($out, $i - 1, 3) === [['Out'], ['Out'], ['Out']]) {
        continue;
    }
    foreach ($row as $v) {
        echo "$k: $v\n";
    }
}
2011: Out
2011: arrv
2012: Out
2015: Out
2016: some
2016: none
2016: test
2017: Out
2019: Out
2020: Out
2020: did
2021: Out
2022: Out
2023: Out
2023: did

Forgivably, the first iteration that subtracts 1 from 0 inside of array_slice() causes the sliced result to start from the back of the array (-1 means start the slice from the second last element). Because only a 3-element slice will be disqualified, this is safe operation. In other words, the first and last element of the array will never be disqualified.

Here is the same technique without manually incrementing the counter variable. Demo

foreach (array_keys($out) as $i => $k) {
    if (array_slice($out, $i - 1, 3) === [['Out'], ['Out'], ['Out']]) {
        continue;
    }
    foreach ($out[$k] as $v) {
        echo "$k: $v\n";
    }
}

If you are actually trying to "pop-out" these unwanted rows, then unset() row by their key. Demo

$i = 0;
foreach ($out as $k => $row) {
    if (array_slice($out, $i - 1, 3) === [['Out'], ['Out'], ['Out']]) {
        unset($out[$k]);
        continue;
    }
    ++$i;
}
var_export($out);

3 Comments

You could also use max($i -1, 0)
No, that could potentially be problematic -- on the first iteration, that would grab the first three elements, but it should only grab two elements.
Of course, I missed that
0

I'll ignore the error

array_keys() expects parameter 1 to be array, integer given

because it's only outputting some debugging information; what you need to resolve is the logic used to detect the "islands" in your sequence of years.

This code is not elegant, won't work for all combinations of the data, and doesn't split up the array data - as that's not the issue here - but hopefully it will help you:

$result = [];
$inSequence = false;
$prev = null;

foreach ($out as $year => $data) {
  if (str_contains($data[0], "Out")) {
    if (!$inSequence) {
      $result[$year] = $data; // Save first of sequence
      $inSequence = !$inSequence; // toggle as in sequence
    }
  } else {
    // have we interrupted the sequence, stash last 'Out' if we have one
    if ($prev) {
        $result[$prev] = $out[$prev];
        $inSequence = !$inSequence; // toggle out of a sequence
    }
    // add current data that does not start with 'Out'
    $result[$year] = $data;
  }
  // Remember current item in case we need it next time round, or to add it on the end.
  $prev = $year;
}

// You'll need to do some checking to see if you actually need to add this on the end, but for the example data we do.
$result[$prev] = $out[$prev];

print_r($result);

What this code does is to maintain a status of when we enter a sequence of "Out" values, and keeps track of the "last" item we looked at in case we need to insert it into the $result array because the current element is not an "Out".

The output of this code is:

Array
(
    [2011] => Array
        (
            [0] => Out
            [1] => arrv
        )
    [2015] => Array
        (
            [0] => Out
        )
    [2016] => Array
        (
            [0] => some
            [1] => none
            [2] => test
        )
    [2017] => Array
        (
            [0] => Out
        )
    [2023] => Array
        (
            [0] => Out
            [1] => did
        )
)

Let me know if you need anything clarified.

When I ran your code I see this output with error messages:

year = 2011, data = Array
(
    [0] => Out
    [1] => arrv
)

year = 2012, data = Array
(
    [0] => Out
)

2012 contains the string; index is 1

   WARNING  Undefined array key 0 in phar:///Applications/Tinkerwell.app/Contents/Resources/tinkerwell/tinker.phar/src/Shell/CustomExecutionLoopClosure.php(38) : eval()'d code on line 20.

   WARNING  Trying to access array offset on null in phar:///Applications/Tinkerwell.app/Contents/Resources/tinkerwell/tinker.phar/src/Shell/CustomExecutionLoopClosure.php(38) : eval()'d code on line 20.

   DEPRECATED  str_contains(): Passing null to parameter #1 ($haystack) of type string is deprecated 

... etc

2 Comments

Thanks, this feels like the right answer, I'll accept it if none better is given. While most of return values are under my control, the format isn't. So, array_search in PHP returns an index that we can not use?
I saw multiple errors in your code when I tried to run it, so i focussed on writing some new code just to demonstrate the logic which might help you fix yours.

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.