[Journal - Walking Arrays]

Walking Arrays

Sunday, February 12, 2006

Question: How do you walk through all elements in a .NET array?

Answer: Now, that's easy, just use foreach.

Question: What if, for each iteration, you need information about where you currently are in the array? In other words, how do you keep track of the indices? Especially, if the array rank (number of dimensions) is unknown?

Answer: using a C# 2.0 iterator, roll you own generic enumerator that yields the indices for each pass in an array:

public static IEnumerable<int[]> ArrayIndices
    (Array ar)
{
    Check.Reference(ar);
    if(ar.Length > 0) // total length (product)
    {
        int[] lowerBounds = Ary.GetLowerBounds(ar);
        int[] upperBounds = Ary.GetUpperBounds(ar);
        int[] indices = new int[ar.Rank];
        bool fContinue = true;
        while(fContinue)
        {
            int iDimLowest = ar.Rank - 1;
            for(int i = lowerBounds[iDimLowest];
                i <= upperBounds[iDimLowest];
                i++)
            {
                indices[iDimLowest] = i;
                yield return indices;
            }
            for(int iDimTmp = iDimLowest;
                iDimTmp >= 0;
                iDimTmp--)
            {
                if(indices[iDimTmp] >= upperBounds[iDimTmp])
                {
                    indices[iDimTmp] = lowerBounds[iDimTmp];
                    if(0 == iDimTmp)
                    {
                        fContinue = false;
                        break;
                    }
                }
                else
                {
                    indices[iDimTmp]++;
                    break;
                }
            }
        }
    }
}

Note that since the array rank is part of the static type information, we're working with System.Array here, in order to support any number of dimensions. Consequently, some of the new Gregor.NET utilities that use Walk.ArrayIndices cannot use static type information for their return type. For example, while there is a generic version of Walk.Select that uses type parameters for its generic TransformCallback parameter, it accepts as well as returns System.Array as its data.

public static Array Select<SourceType, TargetType>
    (Array aIn, TransformCallback<SourceType, TargetType> cb)
{
    int[] lengths = Ary.GetLengths(aIn);
    int[] lowerBounds = Ary.GetLowerBounds(aIn);
    Array aRet = Array.CreateInstance(typeof(TargetType),
                                      lengths, lowerBounds);
    foreach(int[] indices in Walk.ArrayIndices(aIn))
    {
        SourceType src = (SourceType) aIn.GetValue(indices);
        TargetType res = cb(src);
        aRet.SetValue(res, indices);
    }
    return aRet;
}

What's needed is a (.NET-framework-defined) special generic class for arrays, which has the element type as its type parameter, but does not disclose any rank information (ig., class Array<T> : Array).

Examples of new utilities (all in Gregor.Core) include: