tags:

views:

431

answers:

3

Hi,

What is the most efficient way of setting values in C# multi-dimensional arrays using a linear index? For example given an array...

int[,,] arr2 = {   {{0,1,2}, {3,4,5}, {6,7,8}}
          , {{9,10,11}, {12,13,14}, {15,16,17}}
                , {{18,19,20}, {21,22,23}, {24,25,26}}
  };

How do I set all the elements to 30 using a linear index ...

//This code does not work
for (int i = 0; i < arr.Length; i++)
{
    arr.SetValue(30, i);
}

Apparently the SetValue() above does not work with multidimensional arrays.

Here is the best solution that I could come up with...

EDIT: Added some clarifications to the code...

static class Program
{
    static void Main(string[] args)
    {
        //Sample input. 
        int[,,] arr2 = {   {{0,1,2}, {3,4,5}, {6,7,8}}
                  , {{9,10,11}, {12,13,14}, {15,16,17}}
                        , {{18,19,20}, {21,22,23}, {24,25,26}}
          };

        int[] arr1 = { 1, 2, 3, 4 };

        setElementsTo30(arr2);
        setElementsTo30(arr1);

    }

    //Must be able to process int arrays of arbitrary dimensions and content
    private static void setElementsTo30(Array arr)
    {
        IList<int> cumulativeLength = getCumulativeLengths(arr);

        for (int i = 0; i < arr.Length; i++)
        {
            SetValue(arr, i, 30, cumulativeLength);
        }
    }

    public static void SetValue(this Array arr, int index, object value, IList<int> cumulativeLength)
    {
        int[] arrayIndex = new int[arr.Rank];

        for (int dim = arr.Rank-1; dim >= 0; dim--)
        {
            arrayIndex[dim] = index / cumulativeLength[dim] % arr.GetLength(dim);
        }

        arr.SetValue(value, arrayIndex);
    }

    private static IList<int> getCumulativeLengths(Array arr)
    {
        List<int> lengths = new List<int>(arr.Rank);

        for (int dim = 0; dim < arr.Rank; dim++)
        {
            int prod = 1;
            for (int i = dim + 1; i < arr.Rank; i++)
            {
                prod *= arr.GetLength(i);
            }
            lengths.Add(prod);
        }

        return (IList<int>)lengths;
    }
}

Is there a way to do the same more efficiently and possibly using something provided by the framework itself (i.e. something which can be used without much hassle.)

Thanks,
SDX2000.

A: 

SetValue() should work. Take a look at this for a little more inspiration.

EDIT: Could you not just do

{{30,30,30}, {30,30,30}, {30,30,30}}
 , {{30,30,30}, {30,30,30}, {30,30,30}}
  , {{30,30,30}, {30,30,30}, {30,30,30}

}

As a side note, are you sure you want to return an IList<int> from getCumulativeLengths?

I always thought, be generous on input, and strict on output.

Nicholas Mancuso
1. Yes I am restricted to rectangular arrays 2. No returning IList<int> was a hasty decision. In the course of professional programming I would have returned a read only collection. 3. My moto- be strict on input as well as output. I would have provided some refs if I could remember :(
SDX2000
Hmm... I now realize that I was a tad hasty in replying. I'm not entirely sure you can set everything in a linear fashion. You may have to go to a nested for loop.I'll keep reading.
Nicholas Mancuso
@EDIT - Actually this is just a demonstration program I do not have apriori information on the input array (my code will be used as a library).
SDX2000
A: 

Do you know how many tuples will exist initially? If you have say a matrix with dimensions a x b x c x d, couldn't you use the following to get a list of all the indices:

for i=0 to (a*b*c*d)

       Array[i % a, (i/a) % b, (i/(a*b) % c, i / (a*b*c)] = 30

So that as the counter rolls over various bounds, each subsequent index is increased. If there are more, this does generalize to an n-tuple simply be multiplying previous values. One could reverse the arithmetic of the indices if one wanted to traverse in a different way.

JB King
@JB the arrays can have any number of dimensions.
SDX2000
+1  A: 

why do you need the IList ?

static void SetValue2(this Array a, object value, int i) {
    int[] indices = new int[a.Rank];
    for (int d = a.Rank - 1; d >= 0; d--) {
        var l = a.GetLength(d);
        indices[d] = i % l;
        i /= l
    }
    a.SetValue(value, indices);
}

Test Code:

static void Main(string[] args) {
    int[, ,] arr2 = {   
        {{0,1,2}, {3,4,5}, {6,7,8}}, 
        {{9,10,11}, {12,13,14}, {15,16,17}}, 
        {{18,19,20}, {21,22,23}, {24,25,26}}
    };
    for (int i = 0; i < arr2.Length; i++) {
        arr2.SetValue2(30, i);
    }
}
Jimmy
Ok I finally got around to this and I have to say you have done a good job. Thanks for the help. My first version had an internal for loop which calculated the cumulative size for the lower dimensions I saved these totals to a list to improve (time) efficiency.
SDX2000