www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - foreach / mutating iterator - How to do this?

reply =?iso-8859-1?Q?Robert_M._M=FCnch?= <robert.muench saphirion.com> writes:
I have two foreach loops where the inner should change the iterator 
(append new entries) of the outer.

foreach(a, candidates) {
	foreach(b, a) {
		if(...) candidates ~= additionalCandidate;
	}
}

The foreach docs state that the collection must not change during iteration.

So, how to best handle such a situation then? Using a plain for loop?

-- 
Robert M. Münch
http://www.saphirion.com
smarter | better | faster
Jun 25 2018
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Jun 25, 2018 at 05:29:23PM +0200, Robert M. Münch via
Digitalmars-d-learn wrote:
 I have two foreach loops where the inner should change the iterator
 (append new entries) of the outer.
 
 foreach(a, candidates) {
 	foreach(b, a) {
 		if(...) candidates ~= additionalCandidate;
 	}
 }
 
 The foreach docs state that the collection must not change during
 iteration.
 
 So, how to best handle such a situation then? Using a plain for loop?
[...] Yes. T -- The fact that anyone still uses AOL shows that even the presence of options doesn't stop some people from picking the pessimal one. - Mike Ellis
Jun 25 2018
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Monday, June 25, 2018 17:29:23 Robert M. Münch via Digitalmars-d-learn 
wrote:
 I have two foreach loops where the inner should change the iterator
 (append new entries) of the outer.

 foreach(a, candidates) {
   foreach(b, a) {
       if(...) candidates ~= additionalCandidate;
   }
 }

 The foreach docs state that the collection must not change during
 iteration.

 So, how to best handle such a situation then? Using a plain for loop?
Either that or create a separate array containing the elements you're adding and then append that to candidates after the loop has terminated. Or if all you're really trying to do is run an operation on a list of items, and in the process, you get more items that you want to operate on but don't need to keep them around afterwards, you could just wrap the operation in a function and use recursion. e.g. foreach(a, candidates) { doStuff(a); } void func(T)(T a) { foreach(b, a) { if(...) func(additionalCandidate); } } But regardless, you can't mutate something while you're iterating over it with foreach, so you're either going to have to manually control the iteration yourself so that you can do it in a way that guarantees that it's safe to add elements while iterating, or you're going to have to adjust what you're doing so that it doesn't need to add to the list of items while iterating over it. The big issue with foreach is that if it's iterating over is a range, then it copies it, and if it's not a range, it slices it (or if it defines opApply, that gets used). So, foreach(e; range) gets lowered to foreach(auto __c = range; !__c.empty; __c.popFront()) { auto e = __c.front; } which means that range is copied, and it's then unspecified behavior as to what happens if you try to use the range after passing it to foreach (the exact behavior depends on how the range is implemented), meaning that you really shouldn't be passing a range to foreach and then still do anything with it. If foreach is given a container, then it slices it, e.g. foreach(e; container) foreach(auto __c = container[]; !__c.empty; __c.popFront()) { auto e = __c.front; } so it doesn't run into the copying problem, but it's still not a good idea to mutate the container while iterating. What happens when you try to mutate the container while iterating over a range from that container depends on the container, and foreach in general isn't supposed to be able to iterate over something while it's mutated. Dynamic and associative arrays get different lowerings than generic ranges or containers, but they're also likely to run into problems if you try to mutate them while iterating over them. So, if using a normal for loop instead of foreach fixes your problem, then there you go. Otherwise, rearrange what you're doing so that it doesn't need to add anything to the original list of items in the loop. Either way, trying to mutate what you're iterating over is going to cause bugs, albeit slightly different bugs depending on what you're iterating over. - Jonathan M Davis
Jun 25 2018
prev sibling parent =?iso-8859-1?Q?Robert_M._M=FCnch?= <robert.muench saphirion.com> writes:
On 2018-06-25 15:29:23 +0000, Robert M. Münch said:

 I have two foreach loops where the inner should change the iterator 
 (append new entries) of the outer.
 
 foreach(a, candidates) {
 	foreach(b, a) {
 		if(...) candidates ~= additionalCandidate;
 	}
 }
 
 The foreach docs state that the collection must not change during iteration.
 
 So, how to best handle such a situation then? Using a plain for loop?
Answering myself: If you implement an opApply using for() or while() etc. with a mutating aggregate, foreach can be used indirectly with mutating aggregates. Works without any problems. -- Robert M. Münch http://www.saphirion.com smarter | better | faster
Jun 25 2018