tags:

views:

176

answers:

5

It's pretty common - especially as you try to make your code become more data-driven - to need to iterate over associated collections. For instance, I just finished writing a piece of code that looks like this:

string[] entTypes = {"DOC", "CON", "BAL"};
string[] dateFields = {"DocDate", "ConUserDate", "BalDate"};
Debug.Assert(entTypes.Length == dateFields.Length);

for (int i=0; i<entTypes.Length; i++)
{
    string entType = entTypes[i];
    string dateField = dateFields[i];
    // do stuff with the associated entType and dateField
}

In Python, I'd write something like:

items = [("DOC", "DocDate"), ("CON", "ConUserDate"), ("BAL", "BalDate")]
for (entType, dateField) in items:
   # do stuff with the associated entType and dateField

I don't need to declare parallel arrays, I don't need to assert that my arrays are the same length, I don't need to use an index to get the items out.

I feel like there's a way of doing this in C# using LINQ, but I can't figure out what it might be. Is there some easy method of iterating across multiple associated collections?

Edit:

This is a little better, I think - at least, in the case where I have the luxury of zipping the collections manually at declaration, and where all the collections contain objects of the same type:

List<string[]> items = new List<string[]>
{
    new [] {"DOC", "DocDate"},
    new [] {"CON", "ConUserDate"},
    new [] {"SCH", "SchDate"}
};
foreach (string[] item in items)
{
    Debug.Assert(item.Length == 2);
    string entType = item[0];
    string dateField = item[1];
    // do stuff with the associated entType and dateField
}
+3  A: 

In .NET 4.0 they're adding a "Zip" extension method to IEnumerable, so your code could look something like:

foreach (var item in entTypes.Zip(dateFields, 
    (entType, dateField) => new { entType, dateField }))
{
    // do stuff with item.entType and item.dateField
}

For now I think the easiest thing to do is leave it as a for loop. There are tricks whereby you can reference the "other" array (by using the overload of Select() that provides an index, for example) but none of them are as clean as a simple for iterator.

Here's a blog post about Zip as well as a way to implement it yourself. Should get you going in the meantime.

Matt Hamilton
That's nice, looking forward to it
johnc
+2  A: 

Create a struct?

struct Item
{
    string entityType;
    string dateField;
}

Pretty much the same as your Pythonic solution, except type-safe.

Smashery
A: 

You can use the pair and a generic List.

List<Pair> list = new List<Pair>();

list.Add(new Pair("DOC", "DocDate"));
list.Add(new Pair("CON", "ConUserDate"));
list.Add(new Pair("BAL", "BalDate"));

foreach (var item in list)
{
    string entType = item.First as string;
    string dateField = item.Second as string;

    // DO STUFF
}

Pair is part of the Web.UI, but you can easily create your own custom class or struct.

David Basarab
That's not less code than I'm presently writing, though; in fact, it's more. It also breaks down if I have more than two associated lists.
Robert Rossney
A: 

If you just want to declare the lists inline, you can do that in one step:

var entities = new Dictionary<string, string>() {
    { "DOC", "DocDate" },
    { "CON", "ConUserDate" },
    { "BAL", "BalDate" },
};
foreach (var kvp in entities) {
    // do stuff with kvp.Key and kvp.Value
}

If they're coming from other things, we have a bunch of extension methods to build dictionaries from various data structures.

Ken
Two problems with that: 1) it can't handle more than two associated lists, 2) the Dictionary imposes its own ordering when iterating over the keys. But see my edit.
Robert Rossney
+1  A: 

This is realy a variation on the other themes, but this would do the trick also...

var items = new[]
          {
              new { entType = "DOC", dataField = "DocDate" },
              new { entType = "CON", dataField = "ConUserData" },
              new { entType = "BAL", dataField = "BalDate" }
          };

foreach (var item in items)
{
    // do stuff with your items
    Console.WriteLine("entType: {0}, dataField {1}", item.entType, item.dataField);
}
Martin Peck
This is the right answer.
mquander
Unless you subscribe to the DRY principle, which this violates in spades. If the list contains 50 items, that's a lot of redundant code - especially since on every line after the first, the field names don't do anything except create compile-time errors if misspelled.
Robert Rossney