2

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.

5 Answers 5

2

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);
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

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.
1

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.

1 Comment

@JB the arrays can have any number of dimensions.
0

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.

3 Comments

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 :(
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.
@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).
0
    public static void CopyToMultidimensionalArray(this IList<object> source, Array target, IList<int> dimensions)
    {
        var indices = new int[dimensions.Count];
        for (var i = 0; i < source.Count; i++)
        {
            var t = i;
            for (var j = indices.Length - 1; j >= 0; j--)
            {
                indices[j] = t % dimensions[j];
                t /= dimensions[j];
            }

            target.SetValue(source[i], indices);
        }
    }

Comments

0

I came across a similar problem and found a solution for .NET 5+ using the MemoryMarshal and Unsafe utility classes to achieve efficient linear indexing even for arrays with reference types. Here is a simple example:

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;

// prepare multi dimensional array
var data = new string[,] {
    { "a", "b" },
    { "c", "d" },
    { "e", "f" }
};

// get single dimensional span from array
var span = MemoryMarshal.CreateSpan(
    reference: ref Unsafe.As<byte, string>(ref MemoryMarshal.GetArrayDataReference(data)), 
    length: data.Length);

// print array content before editing
Console.WriteLine("Original array:");
PrintArrayContent(data);

// update array using linear index
span[2] = "modified!!";

// print array content after editing
Console.WriteLine("\nModified array:");
PrintArrayContent(data);

void PrintArrayContent<T>(T[,] array)
{
    var sb = new StringBuilder();

    for (int dim0 = 0; dim0 < array.GetLength(0); dim0++)
    {
        for (int dim1 = 0; dim1 < array.GetLength(1); dim1++)
        {
            sb.Append(array[dim0, dim1] + ", ");
        }

        sb.AppendLine();
    }

    Console.WriteLine(sb);
}

The console output is:

Original array:
a, b, 
c, d, 
e, f,

Modified array:
a, b, 
modified!!, d, 
e, f, 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.