Question about Iteration and forEach

N

news.microsoft.com

I am looping through an iteration and I would like to test the next item but
if its not the one that I want how do I put it back so that when my foreach
continues it is in the next iteration?

Bill
 
M

Michael A. Covington

news.microsoft.com said:
I am looping through an iteration and I would like to test the next item
but if its not the one that I want how do I put it back so that when my
foreach continues it is in the next iteration?

I'm not sure what you mean by "put it back". Can you explain?
 
P

Peter Duniho

I am looping through an iteration and I would like to test the next item
but
if its not the one that I want how do I put it back so that when my
foreach
continues it is in the next iteration?

You can't. The "foreach" statement strictly enumerates one by one through
the collection.

If you want the ability to adjust your position within the enumeration,
you can usually just use a normal "for" loop with an index to access
individual items within the collection. Then you can adjust the index as
needed.

I will note that if you are truly enumerating a list, having a need to
look at a specific item and then as a result of that inspection reverse
course and go back to the previous item is generally a bad sign. It
either means that you're not really enumerating the items in the list, and
so enumeration semantics aren't appropriate, or you are enumerating the
list in a manner indicative of poor design.

Hopefully you're in the former case, and you really don't want a true
enumeration, but you should at least consider the possibility of the
latter case. :) For more specific advice, you'd have to post the code
for the actual loop you're talking about.

Pete
 
B

Bruce Wood

I will note that if you are truly enumerating a list, having a need to
look at a specific item and then as a result of that inspection reverse
course and go back to the previous item is generally a bad sign.

Oh, I don't know that I would be so harsh. A good old fashioned
"control-break" iteration is an example of this sort of thing: loop
through the invoices until the customer code changes, process
everything up to the preceding entry, then start with the one you read
last as the "next one" and proceed.

Of course, in a modern language it's often cleaner to build a new data
structure reflecting what you want to do (e.g. a list of customers,
each entry containing a list of invoices for that customer), but the
old way to solve the problem isn't really "bad design"... just
different.

As Peter said, though, it would be better to give us more details
about what you're trying to do, or even post the code. Perhaps we can
come up with a better design, or perhaps we'll reaffirm that what
you're doing is the best way.
 
P

Peter Duniho

Oh, I don't know that I would be so harsh.

I thought I was being pretty open to the occasional exception, actually.
I think I could have been a LOT more harsh. :p
A good old fashioned
"control-break" iteration is an example of this sort of thing: loop
through the invoices until the customer code changes, process
everything up to the preceding entry, then start with the one you read
last as the "next one" and proceed.

Even absent a convenient way in the language to simply collect all of the
items of interest in a new data structure, what would be wrong with just
doing something like this:

int i = 0, count = items.Length;

while (i < count)
{
custid = items.custid;
while (i < count && items.custid == custid)
{
// process the item
i++;
}

// do whatever per-customer stuff here
}

If you don't like the nested loops:

int i = 0, count = items.Length;
int custid = items.custid;

while (i < count)
{
// process the item
i++;

if (i >= count || items.custid != custid)
{
// do whatever per-customer stuff here
}
}

IMHO, either of those is preferable to doing something with the loop index
other than just monotonically increasing it.

Note also that I'm not talking about peeking ahead in the enumeration,
though I think that's also almost never necessary. I'm talking about the
impression I got from the original post that the enumeration would
actually go *backwards* to an item already processed.
Of course, in a modern language it's often cleaner to build a new data
structure reflecting what you want to do (e.g. a list of customers,
each entry containing a list of invoices for that customer), but the
old way to solve the problem isn't really "bad design"... just
different.

I'm not willing to state unequivocably that it is _always_ wrong to modify
the loop counter in the loop other than simply increasing it. I figure,
any time one ever says something is absolute, there's always some joker
out there who's got the one incredibly esoteric counter-example. :) But
I still believe that doing so is generally a sign of poor design. It's
practically never necessary, and doing so makes the code harder to read,
harder to understand, and harder to maintain in a bug-free manner.

Pete
 
J

Jeremy Shovan

Never say you can't. We are programmers we can do anything!!!
Here is a sample.. It is probably not as simple as you would like. but it
does what you want.
I just defined a couple if interfaces; IPeakableEnumerator<T> and
IPeakEnumerable<T>. I then created a collection that derives from List<T>
that implements the interfaces.
And viola. You now have a Peak() method on an enumerator that will get the
next element for you without moving to the next element.


public class Test
{
public static void DoTest()
{
MyList<string> a = new MyList<string>();
a.Add("a");
a.Add("b");
a.Add("c");
a.Add("d");
a.Add("e");

IPeakableEnumerator<string> en = a.GetPeakableEnumerator();
while (en.MoveNext())
{
Console.WriteLine(en.Current);
Console.WriteLine(en.Peak());
}
}
}



public interface IPeakableEnumerator<T> : IEnumerator<T>
{
T Peak();
}
public interface IPeakEnumerable<T>
{
IPeakableEnumerator<T> GetPeakableEnumerator();
}


public class MyList<T> : List<T>, IPeakEnumerable<T>
{
public IPeakableEnumerator<T> GetPeakableEnumerator()
{
return new MyListEnumerator(this);
}

[Serializable, StructLayout(LayoutKind.Sequential)]
public class MyListEnumerator : IPeakableEnumerator<T>, IEnumerator<T>,
IDisposable, IEnumerator
{
private MyList<T> list;
private int index;
private T current;
internal MyListEnumerator(MyList<T> list)
{
this.list = list;
this.index = 0;
this.current = default(T);
}

public void Dispose()
{
}

public bool MoveNext()
{
if (this.index < this.list.Count)
{
this.current = this.list[this.index];
this.index++;
return true;
}
this.index = this.list.Count + 1;
this.current = default(T);
return false;
}

public T Current
{
get
{
return this.current;
}
}
object IEnumerator.Current
{
get
{
if ((this.index == 0) || (this.index == (this.list.Count +
1)))
{
throw new InvalidOperationException("Enumeration
failed");
}
return this.Current;
}
}
void IEnumerator.Reset()
{
this.index = 0;
}

public T Peak()
{
if (this.index >= this.list.Count)
{
return default(T);
}
return this.list[index];
}
}
}


Jeremy Shovan
http://www.jeremyshovan.com
 
P

Peter Duniho

Never say you can't. We are programmers we can do anything!!!
Here is a sample.. It is probably not as simple as you would like. but
it does what you want.

A few thoughts:

1) If you're going to make a "peekable" enumerator, you should
probably spell the word "peek" correctly. At least, if you expect anyone
else to use it, anyway. :)
2) More seriously, you'll note that doing this you have to implement
the entire collection class yourself. There was nothing in the original
post that suggested he was looking to do that, nor is it clear to me that
doing so is always going to be feasible.
3) It's not at all clear to me that simply "peeking" at the next
element in the iteration would be sufficient for the original poster. I
got the impression he wanted to be able to actually move backwards in the
enumeration.
4) I note that the code you've provided doesn't actually use the
"foreach" statement. Again, there seems to be a disconnect between the
original question and the solution you've offered. :)
5) Finally, while I admire your dedication to the problem, it seemsto
me that it would be simpler to simply emulate a "peekable" enumerator
through something like this (assuming, of course, that being able to
"peek" ahead would suffice for the OP's needs):

Item itemPrev = null;

foreach (Item itemCur in MyCollection)
{
if (itemPrev != null)
{
// Do some stuff with itemPrev.
// If we want to "peek" at the next one, look at itemCur
}

itemPrev = itemCur;
}

// Finish up by doing stuff with itemPrev here too. No peeking
allowed!

Note that the above works with any class that implements IEnumerable,
which IMHO would be a major benefit over requiring one's collection class
to be entirely redefined just to support that one aspect of the operation.

My two cents. :)

Pete
 
F

Freddy Potargent

Not want to get in a coding contest but you can also encapsulate this in
an iterator (.Net 2). This way you can reuse the idiom for any
Enumerable collection and it allows you to create almost any traversal
you like or even create collections on the fly.

// Class that will be the return type of the iterator, contains
// the current value and the next 'peeked' value.
class Pair<T>
{
public Pair (T first, T second)
{
Current = first;
Next = second;
}

public T Current;
public T Next;
}

// Defined in some class (unrelated to the collection, maybe a
// static class collecting different iterators :))
static IEnumerable<Pair<T>> peeking_iterator<T> (IEnumerable<T> coll)
{
IEnumerator<T> it = coll.GetEnumerator ();
T first = default(T);

// Get the first element and remember it.
if (it.MoveNext ())
first = it.Current;

// Loop through all elements
while (it.MoveNext ())
{
// Return a pair of the current element and the next element
yield return new Pair<T> (first, it.Current);

first = it.Current;
}

// When necessary return the last element BUT the 'next' element
// will have the default value: 0 for value types like int or
// null for objects.
yield return new Pair<T> (first, default (T));
}

// Test the iterator
public static void tst_iterator ()
{
int[] tstints = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// Note here how the custom iterator is used to wrap
// any IEnumerable collection in a foreach statement.

foreach (Pair<int> p in peeking_iterator (tstints))
{
System.Diagnostics.Debug.WriteLine (
string.Format ("pair: {0}, {1}", p.Current, p.Next));
}

string[] tststrings = new string[] { "abc", "def", "ghi" };

foreach (Pair<string> p in peeking_iterator (tststrings))
{
System.Diagnostics.Debug.WriteLine (
string.Format ("pair: {0}, {1}", p.Current, p.Next));
}
}





Peter Duniho wrote:
[snip]
 
J

Jeremy Shovan

I’m sorry, I didn't mean to offend you! But.

1) If you're going to make a "peekable" enumerator, you should probably
spell the word "peek" correctly. At least, if you expect anyone else to use
it, anyway. :)

--- I will give you this one. But I did write this at 2:00 in the morning.

2) More seriously, you'll note that doing this you have to implement
the entire collection class yourself. There was nothing in the original
post that suggested he was looking to do that, nor is it clear to me that
doing so is always going to be feasible.

--- I did not implement the entire collection

public class MyList<T> : List<T>, IPeakEnumerable<T>
{
public IPeakableEnumerator<T> GetPeakableEnumerator()
{
return new MyListEnumerator(this);
}


This is hardly the entire collection. This is what we call inheritance.
I did however implement the enumerator as an inner class. But that really is
not a lot of code.

3) It's not at all clear to me that simply "peeking" at the next
element in the iteration would be sufficient for the original poster. I got
the impression he wanted to be able to actually move backwards in the
enumeration.

--- The original poster said "I would like to test the next item but if it’s
not the one that I want how do I put it back so that when my foreach
continues it is in the next iteration".

The key parts to this sentence are:
- "I would like to test the next item
- when my foreach continues it is in the next iteration.

My implementation supports exactly this.

4) I note that the code you've provided doesn't actually use the
"foreach" statement. Again, there seems to be a disconnect between the
original question and the solution you've offered. :)

--- Whether you use a 'while' or a 'foreach' you are still enumerating
through the collection. It really doesn't matter which one you use. There is
nothing saying that you must use a foreach rather than a while.

Also, if you used my implementation in a foreach then you would still be
returned an IEnumerator and that is not the type that I want with my
solution. I want an IPeekableEnumerator

5) Finally, while I admire your dedication to the problem, it seems to
me that it would be simpler to simply emulate a "peekable" enumerator
through something like this (assuming, of course, that being able to "peek"
ahead would suffice for the OP's needs):

--- If you did this it wouldn't be an enumerator, now would it?


Lastly, if you spent this much effort trying to actually come up with
solutions rather than knocking others for actually trying to solve a problem
you would be much more useful and much more liked. Though my solution is not
as good as the one Freddy Potargent provided, at least it is a solution.




Never say you can't. We are programmers we can do anything!!!
Here is a sample.. It is probably not as simple as you would like. but it
does what you want.

A few thoughts:

1) If you're going to make a "peekable" enumerator, you should
probably spell the word "peek" correctly. At least, if you expect anyone
else to use it, anyway. :)
2) More seriously, you'll note that doing this you have to implement
the entire collection class yourself. There was nothing in the original
post that suggested he was looking to do that, nor is it clear to me that
doing so is always going to be feasible.
3) It's not at all clear to me that simply "peeking" at the next
element in the iteration would be sufficient for the original poster. I
got the impression he wanted to be able to actually move backwards in the
enumeration.
4) I note that the code you've provided doesn't actually use the
"foreach" statement. Again, there seems to be a disconnect between the
original question and the solution you've offered. :)
5) Finally, while I admire your dedication to the problem, it seems to
me that it would be simpler to simply emulate a "peekable" enumerator
through something like this (assuming, of course, that being able to
"peek" ahead would suffice for the OP's needs):

Item itemPrev = null;

foreach (Item itemCur in MyCollection)
{
if (itemPrev != null)
{
// Do some stuff with itemPrev.
// If we want to "peek" at the next one, look at itemCur
}

itemPrev = itemCur;
}

// Finish up by doing stuff with itemPrev here too. No peeking
allowed!

Note that the above works with any class that implements IEnumerable,
which IMHO would be a major benefit over requiring one's collection class
to be entirely redefined just to support that one aspect of the operation.

My two cents. :)

Pete
 
P

Peter Duniho

I’m sorry, I didn't mean to offend you! But.

You didn't offend me. I have no idea why you think you did.
[...]
2) More seriously, you'll note that doing this you have to
implement the entire collection class yourself. There was nothing in
the original post that suggested he was looking to do that, nor is it
clear to me that doing so is always going to be feasible.

--- I did not implement the entire collection

Poor choice of words on my part. My point is that your method requires a
whole new type.
[...]
3) It's not at all clear to me that simply "peeking" at the next
element in the iteration would be sufficient for the original poster. I
got the impression he wanted to be able to actually move backwards in
the enumeration.

--- The original poster said "I would like to test the next item but if
it’s not the one that I want how do I put it back so that when my
foreach continues it is in the next iteration".

The key parts to this sentence are:
- "I would like to test the next item
- when my foreach continues it is in the next iteration.

Well, I admit that there's ambiguity there. But don't agree that your
interpretation is a foregone conclusion. That's why I wrote "it's not at
all clear". Note that I did not write "that's not what the OP wants". I
simply pointed out that your solution may or may not be the one he wants
or needs.
[...]
--- Whether you use a 'while' or a 'foreach' you are still enumerating
through the collection. It really doesn't matter which one you use.
There is nothing saying that you must use a foreach rather than a while.

You mean, other than the fact that the OP says he wants to use "foreach".
Other than that, you mean. Right?
Also, if you used my implementation in a foreach then you would still be
returned an IEnumerator and that is not the type that I want with my
solution. I want an IPeekableEnumerator

That's my point. Your solution is not compatible with the basic "foreach"
statement. When the OP says he wants to use "foreach".
5) Finally, while I admire your dedication to the problem, it seems
to me that it would be simpler to simply emulate a "peekable" enumerator
through something like this (assuming, of course, that being able to
"peek" ahead would suffice for the OP's needs):

--- If you did this it wouldn't be an enumerator, now would it?

The OP didn't ask about having an enumerator. He _did_ ask about using
the "foreach" statement. Which do you suppose is the approach more likely
to meet the OP's request?
Lastly, if you spent this much effort trying to actually come up with
solutions rather than knocking others for actually trying to solve a
problem you would be much more useful and much more liked. Though my
solution is not as good as the one Freddy Potargent provided, at least
it is a solution.

Now, here...you are _completely_ off-base. I think you'll find that I
spend plenty of time offering solutions to others, and in fact very little
of my time is spent "knocking others for actually trying". Note also that
I did NOT "knock" you for trying. I knocked you for offering a solution
that was not, IMHO, all that appropriate or practical. In fact, I
commended you for trying.

So, whatever else you think of my reply, you are absolutely, completely
wrong to characterize it as simply "knocking you for trying", and
absolutely, completely wrong in accusing me of failing to provide
solutions myself.

Pete
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top