4

Is there a way in C# to set each value of a multidimensional array to a specific value without using loops? I found Array.Fill but it seems to work only for a 1D-Array.

Basically what I'm looking for is something like:

double[,,,] arrayToFill = new double[7,8,9,10];
Array.FillWhole(arrayToFill, 1.2345);

Of course one could use nested loops but this looks annoying. Maybe there is a better solution?

Kirill Kobelev
  • 10,252
  • 6
  • 30
  • 51
ewiigno
  • 43
  • 4
  • double[,,,] arrayToFill = {{1,2,3,4},{7,8,9,10}}; – jdweng Aug 18 '22 at 10:40
  • There is no such method. Pretty much all `Array` methods work only on 1D arrays. The obvious option is to write your own extension method and then you can call that on any multidimensional array you want. You could overload it to handle various ranks with explicit loops or you could write a more complex method that could handle any rank. – John Aug 18 '22 at 10:40
  • There is already an answer here: https://stackoverflow.com/questions/11672149/fill-a-multidimensional-array-with-same-values-c-sharp but they use unsafe code. And I agree with the conclusion on the best answer. If it is not performance critical then don't. – Steve Aug 18 '22 at 10:44
  • I was going to write some extension methods but I see that's already been done in one of the answers on the question linked above, so I'll leave it at that. – John Aug 18 '22 at 10:47

3 Answers3

7

(See the last code sample in this answer for the best solution.)

You can actually use a Span<T> to do this, but it looks quite fiddly!

double[,,,] arrayToFill = new double[7, 8, 9, 10];

var data = MemoryMarshal.CreateSpan(
    ref Unsafe.As<byte, double>(ref MemoryMarshal.GetArrayDataReference(arrayToFill)),
    arrayToFill.Length);

data.Fill(1.2345);

foreach (var value in arrayToFill)
{
    Console.WriteLine(value);  // All values are 1.2345
}

You can write a method to encapsulate this:

public static void FillArray<T>(Array array, T value)
{
    var data = MemoryMarshal.CreateSpan(
        ref Unsafe.As<byte, T>(ref MemoryMarshal.GetArrayDataReference(array)),
        array.Length);

    data.Fill(value);
}

Then the call site becomes a lot more readable:

double[,,,] arrayToFill = new double[7, 8, 9, 10];

FillArray(arrayToFill, 1.2345);

foreach (var value in arrayToFill)
{
    Console.WriteLine(value);  // All values are 1.2345
}

If you want to get more fancy you can encapsulate this in an extension method:

public static class ArrayExt
{
    public static void Fill<T>(this Array array, T value)
    {
        var data = MemoryMarshal.CreateSpan(
            ref Unsafe.As<byte, T>(ref MemoryMarshal.GetArrayDataReference(array)),
            array.Length);

        data.Fill(value);
    }
}

Then you can call it like so:

double[,,,] arrayToFill = new double[7, 8, 9, 10];

arrayToFill.Fill(1.2345);

foreach (var value in arrayToFill)
{
    Console.WriteLine(value);  // All values are 1.2345
}

IMPORTANT! You must ensure that you use the correct type with which to fill the array. If you specify the wrong type it will fill the array with rubbish, for example:

arrayToFill.Fill(1);

will really mess things up.

In this example you'd need to do arrayToFill.Fill<double>(1); to specify the correct type because otherwise it will infer the wrong type for filling the array.

As per comments from /u/charlieface, you can circumvent this issue by adding strongly-typed overloads for each array dimension, thusly:

This is probably the best approach:

public static class ArrayExt
{
    public static void Fill<T>(this T[,]    array, T value) => fill(array, value);
    public static void Fill<T>(this T[,,]   array, T value) => fill(array, value);
    public static void Fill<T>(this T[,,,]  array, T value) => fill(array, value);
    public static void Fill<T>(this T[,,,,] array, T value) => fill(array, value);

    static void fill<T>(Array array, T value)
    {
        var data = MemoryMarshal.CreateSpan(
            ref Unsafe.As<byte, T>(ref MemoryMarshal.GetArrayDataReference(array)),
            array.Length);

        data.Fill(value);
    }

    // ...etc etc. But does anyone really need more than a 5D array? ;)
}

Then arrayToFill.Fill(1); will work correctly.


Also see this post from /u/charlieface

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • 1
    I wrote this extension method just recently as an answer to a different question https://stackoverflow.com/a/73373888/14868997, it's very very fast. Personally I would just leave it with an `AsSpan` extension and do `array.AsSpan.Fill` – Charlieface Aug 18 '22 at 10:54
  • I'll add that link to my answer! – Matthew Watson Aug 18 '22 at 10:55
  • 1
    To avoid needing to specify type explicitly, you'd need an extension method for each possible rank, as noted in that link – Charlieface Aug 18 '22 at 10:59
  • @Charlieface I undid your edit because it does NOT need to have the type specified in the case where the type of the fill value is correct, because it infers the type of T from the fill value type. – Matthew Watson Aug 18 '22 at 11:07
  • 1
    Ah good point. But sounds risky, instead probably better to have separate extensions for each rank. `arrayToFill.Fill(1);` sounds way to much of a risk to leave as it is. – Charlieface Aug 18 '22 at 11:09
  • @Charlieface I'll add some overloads as a specific example, based on your comments. – Matthew Watson Aug 18 '22 at 11:11
  • presumably also works for all those times when you want an array with lower bound other than `0`. (No, I've needed one either.) – Jodrell Aug 18 '22 at 11:43
  • https://dotnetfiddle.net/MmhGgB – Jodrell Aug 18 '22 at 11:58
3

As mentioned by @MatthewWatson, my extension method in a different answer allows you to get a Span<T> of a two dimensional array.

But it's risky, as it leaves it open to the caller as to which data type to actually fill it with. Instead, create the extension methods with the exact rank you need, for example T[,,]

public static Span<T> AsSpan<T>(this T[,,] array)
{
    return MemoryMarshal.CreateSpan(ref Unsafe.As<byte, T>(ref MemoryMarshal.GetArrayDataReference(array)), array.Length);
}

Then you use Span.Fill

double[,,,] arrayToFill = new double[7,8,9,10];
arrayToFill.AsSpan().Fill(1.2345);

You need a different extension for each rank, such as AsSpan<T>(this T[,] array) and AsSpan<T>(this T[,,] array).

Charlieface
  • 52,284
  • 6
  • 19
  • 43
  • https://dotnetfiddle.net/MmhGgB – Jodrell Aug 18 '22 at 11:58
  • What's your point exactly? I posted this before MatthewWatson edited from the words "IMPORTANT!..." onwards – Charlieface Aug 18 '22 at 12:02
  • apologies, don't have a point exactly. I was just musing on your comment and reusing an `AsSpan()` extension rather than copying the internals. I'm now wondering, could this be a more generic extension that works with any given managed struct? – Jodrell Aug 18 '22 at 12:10
  • Yes you could use a `DateTime[]` also, and it doesn't have to be a struct. This only works on arrays of values, if that's what you were wondering, because that wouldn't make any sense to do it on a single value. Yes your example is a bit neater if you want all ranks. – Charlieface Aug 18 '22 at 12:13
-1

its not loops are amazing on arrays you just got to get used to it.

how to fill a Multi-dim array;

int **inpu**;
for(i...;i++){
  for(j...;j++)
  {
   //here you can set your input value how ever you want
   //input  = i + j; or cin << **input**;
   array[i][j] = **inpu**;
  }
}

some times its better using loops than using a unknown function that probably is using loops to fill your arrays and takes more time the finish the task.