Reputation: 29335
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.
Upvotes: 2
Views: 10025
Reputation: 552
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,
Upvotes: 0
Reputation: 944
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);
}
}
Upvotes: 0
Reputation: 91452
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);
}
}
Upvotes: 2
Reputation: 11910
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.
Upvotes: 1
Reputation: 11877
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.
Upvotes: 0