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].

Community
  • 1
  • 1
tswaehn
  • 387
  • 1
  • 11
  • 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. – CBroe May 01 '14 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. – tswaehn May 01 '14 at 18:26
  • sorry @CBroe. setting it to null doesnt work. as foreach works on a copy. – tswaehn May 01 '14 at 18:31
  • Quote: “(manipulating elements _via reference_ is fine)” – CBroe May 01 '14 at 18:32
  • yeah I did it in the second loop, but had to do it also in the first one :-). Great! – tswaehn May 01 '14 at 18:36
  • @CBroe: where might I put the solution thanks to your suggestions? And how do you get your earned reputation? – tswaehn May 01 '14 at 18:42
  • Well if you want to write down (“self-answer”) how you were able to solve the problem now, in a form that might help other people having a similar issue, then that’s fine with me :-) – CBroe May 01 '14 at 18:45

1 Answers1

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]
Mark Miller
  • 7,442
  • 2
  • 16
  • 22
  • This does only work if I call the foreach loop by reference. As CBroe suggested. Did I miss something? – tswaehn May 01 '14 at 19:12
  • 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. – Mark Miller May 01 '14 at 19:27
  • 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. – tswaehn May 01 '14 at 19:33