3

I want to create an extension method to loop over System.Array with unknown number of dimensions

For now I am using a naive approach:

public static void ForEach<T>(this Array source, Action<T> action)
{
    if(source.Rank == 1)
    {
        for (int w = 0; w < source.GetLength(0); w++)
        {
            action((T)source.GetValue(w));
        }
    }
    else if(source.Rank == 2)
    {
        for (int h = 0; h < source.GetLength(1); h++)
        {
            for (int w = 0; w < source.GetLength(0); w++)
            {
                action((T)source.GetValue(h, w));
            }
        }
    }
    else if(source.Rank == 3)
    {
        // etc
    }
}

I am sure, there is much more elegant way of doing that. But I can not figure it out. How do I generalize that method for unlimited number of dimensions ?

mjwills
  • 23,389
  • 6
  • 40
  • 63
koryakinp
  • 3,989
  • 6
  • 26
  • 56
  • 1
    Possible duplicate of [Why do C# multidimensional arrays not implement IEnumerable?](https://stackoverflow.com/questions/275073/why-do-c-sharp-multidimensional-arrays-not-implement-ienumerablet) – mjwills Aug 14 '18 at 01:16
  • @ChristianGollhardt what is 'fundamentally wrong' with using multidimensional arrays with OOP language ? How do you expect to do tensor manipulation without multidimensional arrays ? – koryakinp Aug 14 '18 at 02:03
  • It seems I have misunderstood you question. I mistaken dimensions with the size of a dimension. @koryakinp – Christian Gollhardt Aug 14 '18 at 02:49

3 Answers3

1

If you don't care about the indices, you can just iterate over a System.Array with absolutely no knowledge of its Rank. The enumerator will hit every element.

public class Program
{
    public static void IterateOverArray(System.Array a)
    {
        foreach (var i in a)
        {
            Console.WriteLine(i);
        }
    }

    public static void Main()
    {
        var tests = new System.Array []
        {
            new int[] {1,2,3,4,5,6,7,8},
            new int[,]
            {
                {1,2},{3,4},{5,6},{7,8}
            },
            new int[,,]
            {
                {  {1,2},{3,4} },
                {  {5,6},{7,8} }
            }
        };


        foreach (var t in tests)
        {
            Console.WriteLine("Dumping array with rank {0} to console.", t.Rank);
            IterateOverArray(t);
        }
    }
}

Output:

Dumping array with rank 1 to console.
1
2
3
4
5
6
7
8
Dumping array with rank 2 to console.
1
2
3
4
5
6
7
8
Dumping array with rank 3 to console.
1
2
3
4
5
6
7
8

Link to DotNetFiddle example

John Wu
  • 50,556
  • 8
  • 44
  • 80
1

For those of you playing at home, this is a little messy but allows you to foreach over a Rank taking advantage of yield

public static IEnumerable<T> GetRank<T>(this Array source,int dimension, params int[] indexes )
{

   var indexList = indexes.ToList();
   indexList.Insert(dimension, 0);
   indexes = indexList.ToArray();

   for (var i = 0; i < source.GetLength(dimension); i++)
   {
      indexes[dimension] = i;
      yield return (T)source.GetValue(indexes);
   }
}

Usage

var test1 = new int[2, 2, 3];
test1[1, 1, 0] = 1;
test1[1, 1, 1] = 2;
test1[1, 1, 2] = 3;
foreach (var item in test1.GetRank<int>(2,1,1))
{
  Console.WriteLine(item);
}

Output

1
2
3

Full demo here

TheGeneral
  • 79,002
  • 9
  • 103
  • 141
-1

You could try a recursive approach, in which you test if the item in the array is itself an array. The for loop logic will be called if the item is iterable, and otherwise you can operate on the item for whatever you need to do. If your object implements ICollection this should be fairly simple.

zcleghern
  • 827
  • 7
  • 21
  • I think zcleghern means something like ``` public static void ForEach(this Array source, Action action) { if (source.Rank == 0) { foreach (var item in source) { action((T) item); } } else { foreach (Array arr in source) { arr.ForEach(action); } } } ``` – ryzngard Aug 14 '18 at 01:22