Click here to Skip to main content
15,891,629 members
Articles / Programming Languages / C#
Article

Iterate two Containers at once with Pairenumerator

Rate me:
Please Sign up or sign in to vote.
3.40/5 (3 votes)
25 Feb 2008CPOL6 min read 26.4K   126   17   1
A enumerable class, that is able to iterate two enumerable collections at once. Either until both or one is done.

Introduction

This article describes an enumerable class that is able to iterate two enumerable collections at once, either until both or one is done.

Background

Pairenumerable.PNG

Recently, I had to fill two Lists (Containers) into a ListView (Control), each one in a separate column. No problem, one may think. Lists are enumerable, and a decent foreach would do the job. And so it is:

C#
private void btTry1_Click(object sender, EventArgs e)
{
    listView.Items.Clear();
    foreach (var item in listA)
    {
        listView.Items.Add(item);
    }
    int counter = 0;
    foreach (var iitem in listB)
    {
        listView.Items[counter].SubItems.Add( iitem.ToString());
        counter+=1;
    }
}

Working out the first list's foreach works fine; however, with the second enumerable it gets ugly. One can alter a ListView's data afterwards. One has to index the ListView's collection while at the same time iterate the List container. That is ugly and how would one guarantee that each nth element of List A is in the same line as the nth element of List B. OK, as long as there is no different thread, there is no threat (normal for GUI). But apply this problem to a concurrently used data structure in a multi-threaded application. It would not be reliable, or require long locks. You can overcome this by enumerating both containers (Lists) at once. That is, calling listA's enumerator's MoveNext() and listB's enumerator's MoveNext(). That can solve the problem and would restrict probable locks to only the inner loop. It is a little bit nicer and more safe... However, it's not slick and neat; there is a lot of stuff to write and to care about, which is error prone — and also the delightful foreach loop is not utilized.

C#
private void btTry2_Click(object sender, EventArgs e)
{
    listView.Items.Clear();
    IEnumerator<string> enumA = listA.GetEnumerator();
    IEnumerator<int> enumB = listB.GetEnumerator();
    while( true )
    {
        // anvance both but quit if anyone reached the end
        bool hasmoreA = enumA.MoveNext();
        bool hasmoreB = enumB.MoveNext();
        if (!( hasmoreA || hasmoreB))
        {
            break;
        }
        else
        {
            listView.Items.Add(new ListViewItem(
                new string[] { enumA.Current, enumB.Current.ToString() })
                );
        }
    }
}

It should be mentioned that in the case of the end condition, the MoveNext() calls have to be moved out of the if. Otherwise, the lazy evaluation of the logical OR strikes back and calls only the first variable's method call, and then once exceeded, the second. It's however quite easy to change between the "longest" (disjunctive) and the "least" (conjunctive) enumeration: by changing the condition between AND and OR.

So, this is also not quite what I like to have in my code ... the truth must be somewhere in between.

Solution

That made me think of Pairs and Pairenumerators. If the enumerator interface works with one variable, why not wrap two variables in a Pair and let it go the same way? The special logic would then be wrapped in some classes. When I searched for these names, I could not find any. So I was asking myself: Is it possible to create an object encapsulating two existing enumerables and make it behave also as enumerable such that one can use it in a foreach loop? I decided to see this as a nice exercise to explore generics a little more. My prerequisite was a Pair<A,B> class or better a struct, as the desired return value. Since I could not find a "Pair" in the first jump, I quickly sketched up this one (Yes, it was quicker than searching + integrating, plus it was another exercise). KeyValuePair<> (from Dictionary) which I found later, would have worked but it has the wrong names:

C#
public struct Pair<T1,T2>
{
    public Pair(T1 frst, T2 sec)
    {
        first= frst;
        second = sec;
    }
    public T1 first;
    public T2 second;
}

That is it. No where-constraints to the generic types. One can use it with virtually everything. It became a struct (value type), since it is so small and therefore better used on the stack. So given the Pair and a to be developed Pairenumerable, let's have a look at how one would expect it to be used:

C#
private void btTry3_Click(object sender, EventArgs e)
{
    listView.Items.Clear();
    foreach (var el in new Pairenumerable<string, int>(listA, listB,cbOr.Checked))
    {
        listView.Items.Add(new ListViewItem(
                // careful: int-var.ToString() works bacause its value type
                // but string-var.ToString() fails if its null (ref type)
                new string[] { el.first, el.second.ToString() })  );
    }
}

That looks neat. The foreach expression is little longer but the benefit is a more automated iteration of two enumerables. One can even select on whether one wants to quit early (if one collection is done) or later (if both collections are done). In the second case, it becomes dangerous, depending on whether the generic parameters are of reference or of value type. If one collection runs out, the default for a reference type is null. Thus, any method call yields an exception. Value types, in contrast, get actual default values. Thus, method calls on them still succeed, despite the sense of the output. Therefore, one may want to write, in a real application, something like this in the loop:

C#
string kto1 = ele.first!=null ? ele.first.ToString() : string.Empty;
string kto2 = ele.second!=null ? ele.second.ToString() : string.Empty;
listView.Items.Add(new ListViewItem( new string[] { kto1, kto2 }));

Now, for the actual Pairenumerable. A few thoughts upfront: What do we expect? We expect it to be enumerable. Which is why it implements IEnumerable<>. We get two element types to iterate with. And we'll have those condense them to one, that'll be used as the type parameter for the IEnumerable<>. Here comes Pair<> into play. That struct is used to make a 2-in-1 in all places that usually would return one variable only. On the other hand, we give the Pairenumerable two members for the enumerable (IEnumerable<>) containers. But we want to get the element type T as type parameters, because we need to build both IEnumerable<T> and IEnumerator<T>. The second is needed for the inner class, the Pairenumerator, and as a return type for the IEnumerable's GetEnumerator() method. When iterating two distinct containers, their size most often differs. So what behaviour would one expect, if one container reaches its end? There are two possibilities: Just quit, OR keep on gong till the second also reaches its end. In the last case, there must be some precaution taken in what to return. It's a Pair that's being returned, but a Pair is always a Pair. So there must be some null or default value for the element. The Pair can be instantiated with any type. Be it a Value type, Reference type, or a basic type. Depending on that, the default value would be: a struct with all members set to 0 (i.e., new DateTime()), the reference null (i.e., string) or 0 itself (i.e., for int). The C# language has for that reason foreseen the keyword default. Applied to a generic variable like a method call, it assigns its type specific default value. This is resembled by the Pairenumerator's Current property. Another aspect is the MoveNext() method. It advances both enumerators which it contains. If one enumerator reaches its end, it has to decide on whether to continue or quit. This is determined on a member variable disjunctive that origins from Pairenumerable's constructor. If set to true, it has an OR semantic in a way that it continues if either one still can move next. Caution has to be used in this case, because an enumerator's MoveNext() must not be called again once it reached the end. Otherwise, it throws an exception. So bool flags prevent this in the Pairenumerator's MoveNext().

The code

C#
public class Pairenumerable<T1, T2>
    : IEnumerable<Pair<T1, T2>>
{
    protected IEnumerable<T1> firstlist;
    protected IEnumerable<T2> secondlist;
    protected bool disjunktiv = true;
    public Pairenumerable(IEnumerable<T1> list1, IEnumerable<T2> list2, bool 

disjunctive_mode)
    {
        firstlist = list1;
        secondlist = list2;
        disjunktiv = disjunctive_mode;
    }

    public class Pairenumerator : IEnumerator<Pair<T1, T2>>
    {
        protected IEnumerator<T1> firstenum;
        protected IEnumerator<T2> secondenum;
        protected bool firstenumhasnext = true;
        protected bool secondenumhasnext = true;
        protected bool disjunktiv;
        public Pairenumerator(IEnumerator<T1> enum1, IEnumerator<T2> enum2, bool mode)
        {
            firstenum = enum1;
            secondenum = enum2;
            disjunktiv = mode;
        }

        public Pair<T1, T2> Current
        {
            get
            {
                return new Pair<T1, T2>(
                    (firstenumhasnext ? firstenum.Current : default(T1)),
                    (secondenumhasnext ? secondenum.Current : default(T2))
                    );
            }
        }


        public void Dispose()
        {
            firstenum.Dispose();
            secondenum.Dispose();
        }


        object System.Collections.IEnumerator.Current
        {
            get { return Current; }
        }

        public bool MoveNext()
        {
            if (firstenumhasnext)
                firstenumhasnext = firstenum.MoveNext();
            if (secondenumhasnext)
                secondenumhasnext = secondenum.MoveNext();
            // hört auf (ret false), wenn einer nicht mehr kann (disj) oder
            // beide nicht mehr können (konjunktiv/!disj)
            return (disjunktiv && (secondenumhasnext || firstenumhasnext))
                     || (!disjunktiv && (secondenumhasnext && firstenumhasnext));
        }

        public void Reset()
        {
            firstenum.Reset();
            firstenumhasnext = true;
            secondenum.Reset();
            secondenumhasnext = true;
        }

    };

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return new Pairenumerator(firstlist.GetEnumerator(), 
                   secondlist.GetEnumerator(), disjunktiv);
    }

    IEnumerator<Pair<T1, T2>> IEnumerable<Pair<T1, T2>>.GetEnumerator()
    {
        return new Pairenumerator(firstlist.GetEnumerator(), 
              secondlist.GetEnumerator(), disjunktiv);
    }

}

Remarks / Problems

From language syntax, the inner class again may have type parameters. But they are not helpful in this case. Making Pairenumerator an inner class gives it access to the identical type parameters which is recommended. These IEnumerable/IEnumerator interfaces require one to implement a zoo of generic and old fashioned non-generic methods. Just delegate the non-generic to the generic ones. But anyway do it, since otherwise older code or non-generics-capable languages can't use it. There should be some way to access the disjunctive variable of the outer class from the inner class.

Quintessence

This is a nice example on how you can make your programming less error prone by utilizing generics. It is though a lot of text to build up these helper classes, but the trade-off between writing the same stuff again and again (plus making errors) and including that file plus using the helper class is towards the second.

Final words

Keep on, or if not yet done, start writing articles. If you've got even a small topic, start an article about it. It will make you and the potential reader more clever. So to say, a win-win situation.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Monaco Monaco
Freelancer in german speaking world

Comments and Discussions

 
GeneralVery nice! Pin
C-codist25-Feb-08 3:13
C-codist25-Feb-08 3:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.