Indexing into arrays of arbitrary rank in C# - c#

I need to iterate over an array of arbitrary rank. This is for both reading and writing, so GetEnumerator will not work.
Array.SetValue(object, int) doesn't work on multidimensional arrays.
Array.SetValue(object, params int[]) would require excessive arithmetic for iterating through the multidimensional space. It would also require dynamic invocation to get around the params part of the signature.
I'm tempted to pin the array and iterate over it with a pointer, but I can't find any documentation that says that multidimensional arrays are guaranteed to be contiguous. If they have padding at the end of a dimension then that won't work. I'd also prefer to avoid unsafe code.
Is there an easy way to sequentially address a multidimensional array using only a single index?

Multidimensional arrays are guaranteed to be contiguous. From ECMA-335:
Array elements shall be laid out within the array object in row-major order (i.e., the elements associated with the rightmost array dimension shall be laid out contiguously from lowest to highest index).
So this works:
int[,,,] array = new int[10, 10, 10, 10];
fixed (int* ptr = array)
{
ptr[10] = 42;
}
int result = array[0, 0, 1, 0]; // == 42

You can use the Rank and GetUpperBound property/method to create an indices array that you can pass to the array's SetValue and GetValue methods:
int[] Indices(Array a, int idx)
{
var indices = new int[a.Rank];
for (var i = 0; i < a.Rank; i++)
{
var div = 1;
for (var j = i + 1; j < a.Rank; j++)
{
div *= a.GetLength(j);
}
indices[i] = a.GetLowerBound(i) + idx / div % a.GetLength(i);
}
return indices;
}
..and use it like so:
for (var i = 0; i < array.Length; i++)
{
var indices = Indices(array, i);
array.SetValue(i, indices);
var val = array.GetValue(indices);
}

Perhaps you could join them all into the a single temporary collection, and just iterate over that.

Related

How can I set the value of each element in a multidimensional System.Array of unknown rank?

I'm trying to serialize multidimensional arrays with any number of dimensions. Note that I'm referring to actual multidimensional arrays (float[,]), not jagged arrays (float[][]).
After serializing the length of each dimension, I can easily serialize each element using foreach (var item in array).
But when deserializing I can't use foreach because I need to assign the values. I know that I'll need to use array.SetValue(object value, params int[] indices), but I can't wrap my head around how to set up a loop to do what you would normally do with one nested for loop for each dimension.
First, serialize the original array using a foreach loop. It will flatten the array.
Then, read the values in the same way the were serialized (code is based on the array's enumerator)
var original = new int[2,2,2] { { { 1, 2 }, { 3, 4} }, { { 5, 6 }, { 7, 8 } } };
var serialized = original.Cast<int>().ToArray();
var originalBounds = Enumerable.Range(0, original.Rank)
.Select(i => original.GetUpperBound(i) + 1).ToArray();
var empty = Array.CreateInstance(typeof(int), originalBounds);
var indices = new int[empty.Rank];
indices[indices.Length - 1]--;
var index = 0;
while (IncArray(empty, indices))
{
empty.SetValue(serialized[index++], indices);
}
private bool IncArray(Array array, int[] indices)
{
int rank = array.Rank;
indices[rank - 1]++;
for (int i = rank - 1; i >= 0; i--)
{
if (indices[i] > array.GetUpperBound(i))
{
if (i == 0)
{
return false;
}
for (int j = i; j < rank; j++)
{
indices[j] = 0;
}
indices[i - 1]++;
}
}
return true;
}
This approach would allow you to read the values from a stream - you don't need to create the serialized array like I have.
Another way is to use Buffer.BulkCopy. Here you'd have to read the entire array, at least into bytes, and calculate the amount of bytes you need to copy. The advantage here is that you avoid all the boxing in the previous method, and that it's simpler:
var bytes = new byte[...]; // read the entire byte array
var empty = Array.CreateInstance(typeof(int), originalBounds);
Buffer.BlockCopy(bytes, 0, empty, 0, bytes.Length);

C# While Loop Unintentional Reference

I am trying to use a while loop to determine when a process is completed, but in order to establish completion, I must compare the previous run with the current run, so I was setting the comparison array to the original... this apparently is creating some sort of reference because when I change a value in the original "Distinct" array, the value also changes in the "InDistinct" comparison array. How can I prevent/work around this? Otherwise my "while(Distinct!=InDistinct)" is useless.
int[,] Distinct = new int[100, 100]; //Establish Distinct Table
int[,] InDistinct= new int[100,100]; //Comparison Array
//Initialize Distinct table to all 0's (not shown)
while (InDistinct != Distinct)
{
InDistinct = Distinct;
for (int j=0; j < DoIt.Sstates.Length; j++)
{
for (; k < DoIt.Sstates.Length; k++)
{
if (DoIt.Sstates[k] == null)
break;
int w = 0;
for (w = 0; w < DoIt.alphabet.Length;w++)
{
if (DoIt.alphabet[w] == '\0')
break;
if (Distinct[j, k] == 0 && Distinct[jim.tLookup(DoIt, j, DoIt.alphabet[w]), jim.tLookup(DoIt, k, DoIt.alphabet[w])] == 1)
Distinct[j, k] = 1;
else if (Distinct[j, k] == 0 && Distinct[jim.tLookup(DoIt, k, DoIt.alphabet[w]), jim.tLookup(DoIt, j, DoIt.alphabet[w])] == 1)
Distinct[j, k] = 1;`
I ended up initiating the InDistinct array above the While loop and copying the Distinct array to it via nested for loops:
int[,] InDistincta= new int[100,100];
while (InDistincta != Distinct)
{
for (int a = 0; a < 100; a++)
{
for (int b = 0; b < 100; b++)
{
InDistincta[a, b] = Distinct[a, b];
}
Isn't this statement at the beginning of your while loop doing this?
while (InDistinct != Distinct)
{
InDistinct = Distinct;
The condition of your loop is checking if the InDistinct array value is not equal to the Distinct value but then makes the two equal by assigning the InDistinct value to that of the Distinct one immediately after.
Arrays are mechanisms that allow you to treat several items as a
single collection. The Microsoft® .NET Common Language Runtime (CLR)
supports single-dimensional arrays, multidimensional arrays, and
jagged arrays (arrays of arrays). All array types are implicitly
derived from System.Array, which itself is derived from System.Object.
This means that all arrays are always reference types which are
allocated on the managed heap, and your app's variable contains a
reference to the array and not the array itself.
https://msdn.microsoft.com/en-us/library/bb985948.aspx
So you need to change
InDistinct = Distinct;
just because after that line, the two names refers the same object.
try to copy target array into to the InDistinct object
targetArray = new string[sourceArray.Length];
sourceArray.CopyTo( targetArray, 0 );
for multi-dimensional arrays
Array.Copy(sourceArray,targetArray, sourceArray.Length);

Create c# int[] with value as 0,1,2,3... length

I like to create an int[] with length X and value it with [0,1,2....X]
e.g.
public int[] CreateAA(int X){}
int[] AA = CreateAA(9) => [0,1,2,3,4,5,6,7,8,9]
is there any easy method? Or have to loop and init value
You can avail the functionality of IEnumerable.
int[] arr = Enumerable.Range(0, X+1).ToArray();
This will create a IEnumerable List for you and .ToArray() will satisfy your int array need.
So for X=9 in your case it would generate the array for [0,1,2,3,4,5,6,7,8,9] (as you need)
Using Enumerable.Range(0, 10).ToArray() is very concise but if you want to create a very large array the ToArray extension method will have to collect the numbers into a buffer that will have to be reallocated multiple times. On each reallocation the contents of the buffer is copied to the new larger buffer. .NET uses a strategy where the size of the buffer is doubled on each reallocation (and the initial buffer has four items).
So if you want to avoid multiple reallocations of the buffer you need to create the array in advance:
int[] aa = new int[10];
for (var i = 0; i < aa.Length; i += 1)
aa[i] = i;
This is the most efficient way of initializing the array.
However, if you need an array of say 100,000,000 consecutive numbers then you should look at a design where you don't have to keep all the numbers in an array to avoid the impact of the memory requirement. IEnumerable<int> is very useful for this purpose because you don't have to allocate the entire sequence but can produce it while you iterate and that is exactly what Enumerable.Range does. So avoiding the array of consecutive numbers in the first place may be even better than thinking about how to create it.
Why make a function when it is already there.
For this specific example, use
int[] AA = Enumerable.Range(0, 10).ToArray();
where 0 is the starting value and 10 (X + 1) is the length of array
So a general one applicable to all
int[] AA = Enumerable.Range(0, X + 1).ToArray();
with function and loop:
static int[] f(int X)
{
int[] a = new int[X+1];
for(int i = 0; i < a.Length; i++)
a[i] = i;
return a;
}
To initialize try this
int x = 10;
Enumerable.Range(0, x)
.Select((v, i) => v + i).ToArray();
For completeness, here is a function that creates an array.
I made it a bit more versatile by having parameters for the min and max value, i.e. CreateArray(0, 9) returns {0,1,2,3,4,5,6,7,8,9}.
static int[] CreateArray(int min, int max) {
int[] a = new int[max - min + 1];
for (int i = 0; i < a.Length; i++) {
a[i] = min + i;
}
return a;
}

Getting a double[] row array of a double[,] rectangular array

Suppose you have an array like:
double[,] rectArray = new double[10,3];
Now you want the fouth row as a double[] array of 3 elements without doing:
double[] fourthRow = new double[]{rectArray[3,0],
rectArray[3,1],
rectArray[3,2]};
Is it possible someway? Even using a Marshal.Something approach?
Thanks!
You can use Buffer.BlockCopy method:
const int d1 = 10;
const int d2 = 3;
const int doubleSize = 8;
double[,] rectArray = new double[d1, d2];
double[] target = new double[d2];
int rowToGet = 3;
Buffer.BlockCopy(rectArray, doubleSize * d2 * rowToGet, target, 0, doubleSize * d2);
LINQ to the rescue:
var s = rectArray.Cast<double>().Skip(9).Take(3).ToArray();
Explanation: Casting a multi-dimensional array flattens it to a single-dimensional array. After that all we need to do is skip to the element we want (the 4th element in the 2-D array resolves to Skip(9)...) and take 3 elements from it).
Why not make a generic extension method?
public static T[] GetRow<T>(this T[,] input2DArray, int row) where T : IComparable
{
var width = input2DArray.GetLength(0);
var height = input2DArray.GetLength(1);
if (row >= height)
throw new IndexOutOfRangeException("Row Index Out of Range");
// Ensures the row requested is within the range of the 2-d array
var returnRow = new T[width];
for(var i = 0; i < width; i++)
returnRow[i] = input2DArray[i, row];
return returnRow;
}
Like this all you have to code is:
array2D = new double[,];
// ... fill array here
var row = array2D.GetRow(4) // Implies getting 5th row of the 2-D Array
This is useful if you're trying to chain methods after obtaining a row and could be helpful with LINQ commands as well.
You probably want to use a jagged array. That is not an array of 10 by 3 but instead an array of arrays.
Something like :
double[][] rectArray;
....
double [] rowArray = rectArray[3];
There are lots of places to learn more about jagged arrays. For example Dynamically created jagged rectangular array
If you must use a rectangular array and just want to simplify the syntax, you can use a method to get the row like so:
double[] fourthRow = GetRow(rectArray, 3);
public static T[] GetRow<T>(T[,] matrix, int row)
{
var columns = matrix.GetLength(1);
var array = new T[columns];
for (int i = 0; i < columns; ++i)
array[i] = matrix[row, i];
return array;
}
Although this is an old thread, an addition to Joseph Sturtevants answer may be useful. His function crashes in case the matrix's first column is not zero, but another integer.
This is e.g. always the case in case of retrieving data from Excel, like
object[,] objects = null;
Excel.Range range = worksheet.get_Range("A1", "C5");
objects = range.Cells.Value; //columns start at 1, not at 0
The GetRow function could be modified like this:
public static T[] GetRow<T>(T[,] matrix, int row, int firstColumn)
{
var columns = matrix.GetLength(1);
var array = new T[columns];
for (int i = firstColumn; i < firstColumn + columns; ++i)
array[i-firstColumn] = matrix[row, i];
return array;
}

How to copy a row of values from a 2D array into a 1D array?

We have the following object
int [,] oGridCells;
which is only used with a fixed first index
int iIndex = 5;
for (int iLoop = 0; iLoop < iUpperBound; iLoop++)
{
//Get the value from the 2D array
iValue = oGridCells[iIndex, iLoop];
//Do something with iValue
}
Is there a way in .NET to convert the values at a fixed first index into a single dimension array (other than by looping the values)?
I doubt it would speed up the code (and it may well make it slower) if the array is only being looped once. But if the array was being heavily manipulated then a single dimension array would be more efficient than a multi dimension array.
My main reason for asking the question is to see if it can be done and how, rather than using it for production code.
The following code demonstrates copying 16 bytes (4 ints) from a 2-D array to a 1-D array.
int[,] oGridCells = {{1, 2}, {3, 4}};
int[] oResult = new int[4];
System.Buffer.BlockCopy(oGridCells, 0, oResult, 0, 16);
You can also selectively copy just 1 row from the array by providing the correct byte offsets. This example copies the middle row of a 3-row 2-D array.
int[,] oGridCells = {{1, 2}, {3, 4}, {5, 6}};
int[] oResult = new int[2];
System.Buffer.BlockCopy(oGridCells, 8, oResult, 0, 8);
Edit:
I realized there is a way! Granted, it's probably not worth it. Use unsafe code. Full example, showing both ways, with unsafe below:
public class MultiSingleUnsafe
{
public static unsafe void Main(String[] a)
{
int rowCount = 6;
int iUpperBound = 10;
int [,] oGridCells = new int[rowCount, iUpperBound];
int iIndex = rowCount - 2; // Pick a row.
for(int i = 0; i < iUpperBound; i++)
{
oGridCells[iIndex, i] = i;
}
for (int iLoop = 0; iLoop < iUpperBound; iLoop++)
{
//Get the value from the 2D array
int iValue = oGridCells[iIndex, iLoop];
Console.WriteLine("Multi-dim array access iValue: " + iValue);
//Do something with iValue
}
fixed(int *lastRow = &(oGridCells[iIndex,0]))
{
for (int iLoop = 0; iLoop < iUpperBound; iLoop++)
{
int iValue = lastRow[iLoop];
Console.WriteLine("Pointer access iValue: " + iValue);
}
}
}
}
There is no way I know to cast a multiple-dimensional array into a single-dimensional one in C#. Of course, you could create a new single-dimensional array and copy into it. But I don't think this will get a performance benefit even if you loop over the values multiple times. As Daren said, internally it's all pointer arithmetic anyway. If you want to be certain, profile it.
I'd be suprised if it were possible: I bet oGridCells[iIndex, iLoop] is just a sort of shorthand (internally, in MSIL) for oGridCells[iIndex * iLoop], and that multidimensional arrays are syntactic sugar for this.
To answer your question: No. You will have to loop the values.
You cannot get a reference to each array. You can, however, use a jagged array.
"But if the array was being heavily manipulated then a single dimension array would be more efficient than a multi dimension array."
I did some profiling of exactly this last summer and was surprised to see no significant difference in performance between a 2D and 1D array.
I didn't test the performance of a jagged array.
You can try this:
int[,] twoD = new int[2,2];
twoD[0, 0] = 1;
twoD[0, 1] = 2;
twoD[1, 0] = 3;
twoD[1, 1] = 4;
int[] result = twoD.Cast<int>().Select(c => c).ToArray();
The result will be an integer array with data:
1, 2, 3, 4
It seems it's about 30% faster to use Span.CopyTo on .NET Core and .NET 5+, than to use Buffer.BlockCopy as in #BlueMonkMN's answer.
public static T[] CopyToArray<T>(ref T start, int length)
{
var target = new T[length];
var span = MemoryMarshal.CreateReadOnlySpan(ref start, length);
span.CopyTo(target.AsSpan());
return target;
}
You would use it like this (obviously ensure you don't overrun the array, as there are no bounds checks).
var singleArray = CopyToArray(ref oGridCells[0, 0], oGridCells.Length);
dotnetfiddle
BenchmarkDotNet results
Method
Mean
Error
StdDev
BlueMonkMN
18.84 ns
0.220 ns
0.206 ns
Charlieface
12.31 ns
0.196 ns
0.183 ns

Categories