0

First of all, its pretty similar to that "Unsetting elements from array within foreach"

However I do not know a neat way around it. Is there any way to mark items in a foreach loop as "used" or "deleted"?

I basically have an array of objects which are alike in some object-properties, but not identical. I want to join the information of similar objects. The idea is as follows

  1. go through the array by picking one object
  2. with each picked object go again trough the array and find similar ones
  3. once found a similar one, join the information and remove the similar object

thats it.

It works some kind of expected but there is a problem (as usual :-). Here it comes:

Although I removed the similar object in the second loop. The first loop is still picking the removed object. That seems to be because foreach loops on some kind of copy of the array.

For discussion/presentation I adopted the code as follows by just looking a strings and join them. Assume we have an Array like this

$test = array( "a", "b", "c", "b", "c" );

The expected result:

array( "a", "bb", "cc" );

Here is the code:

$test = array( "a", "b", "c", "b", "c" );

foreach ($test as $keyA=>$valueA){
  echo "I am at item ".$valueA." [".$keyA."]<br>";

  foreach ($test as $keyB=>$valueB){

    if ($keyA != $keyB){
      // if not comparing to itself
      echo "=> comparing to ".$valueB." [".$keyB."]";

      if (strcmp($valueA,$valueB)==0){
        // is the same string, ... join and remove
        echo "-- joined and removed [".$keyB."]";
        $test[$keyA]=$valueA.$valueB;
        unset($test[$keyB]);      
      }
      echo "<br>";
    }
  }

}

and the actual result

I am at item a [0]
=> comparing to b [1]
=> comparing to c [2]
=> comparing to b [3]
=> comparing to c [4]
I am at item b [1]
=> comparing to a [0]
=> comparing to c [2]
=> comparing to b [3]-- joined and removed [3]
=> comparing to c [4]
I am at item c [2]
=> comparing to a [0]
=> comparing to bb [1]
=> comparing to c [4]-- joined and removed [4]
I am at item b [3]
=> comparing to a [0]
=> comparing to bb [1]
=> comparing to cc [2]
I am at item c [4]
=> comparing to a [0]
=> comparing to bb [1]
=> comparing to cc [2]

Although Item [3] and [4] where removed it is still picking [3] and [4].

7
  • You should not manipulate an array while looping over it with foreach, at least not in its structure (manipulating elements via reference is fine) – of course that will give you unexpected results. Set your elements that you’re done with to null, and ignore null values in further loop iterations. Commented May 1, 2014 at 18:24
  • wow that is quick and saves my day. I just read that foreach loops allways on a copy, that explains the behavior. And your suggestions with setting it to null, ... I'll give it a try. Commented May 1, 2014 at 18:26
  • sorry @CBroe. setting it to null doesnt work. as foreach works on a copy. Commented May 1, 2014 at 18:31
  • Quote: “(manipulating elements via reference is fine)” Commented May 1, 2014 at 18:32
  • yeah I did it in the second loop, but had to do it also in the first one :-). Great! Commented May 1, 2014 at 18:36

1 Answer 1

0

Yes, the foreach loop is working on a copy, but when you are calling unset() you are still, in fact, unsetting that element, even though you haven't changed the copy that the foreach loop is iterating through... so even though the actual array has been changed, the foreach loop doesn't know that, so to speak... you could simply add this check to your code, skipping the iteration if that element has been unset:

foreach ($test as $keyA=>$valueA){

    if (!isset($test[$keyA])) continue;     

    echo "I am at item ".$valueA." [".$keyA."]<br>";

    // etc...
}

The output becomes this, which if I understand your question is what you're looking for:

I am at item a [0]
=> comparing to b [1]
=> comparing to c [2]
=> comparing to b [3]
=> comparing to c [4]
I am at item b [1]
=> comparing to a [0]
=> comparing to c [2]
=> comparing to b [3]-- joined and removed [3]
=> comparing to c [4]
I am at item c [2]
=> comparing to a [0]
=> comparing to bb [1]
=> comparing to c [4]-- joined and removed [4]
Sign up to request clarification or add additional context in comments.

3 Comments

This does only work if I call the foreach loop by reference. As CBroe suggested. Did I miss something?
You shouldn't need to call the loop by reference. It is using the real value of $test, and borrowing the value of $keyA to check if $test[$keyA] isset. If it's not, it just skips that element in the "copy" array the foreach loop is using.
Jep. Now I got it. You are right this works. Although someone gets confused that foreach works on the copy while isset() works on the original array. So the call by reference for me is the better solution also when it comes to further processing of the data. Because the $valueA represents the copy while $test[$keyA] has the real data.

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.