3

I have a multidimensional array, which I want to initialize in a simple and fast way:

double[,,] arr = new double[4,5,6];
// doesn't work by design
foreach(double d in arr)
   d = ... ; // my initialization value

This obviously doesn't works. But I would like to have a generic function for setting all array values to a choosen default. With own classes, I could write a special constructor, but with value types I've no real idea. With C++, I could access all items in a linear way with one for loop, but in C# I think I've to use as much for loops as I have dimensions. I've no better solution at the moment ( or I'm using unsafe code and pointer arithmetics, which would probably work.).

Is there a more elegant way for doing this ?

Nick
  • 1,417
  • 1
  • 14
  • 21
Florian Storck
  • 509
  • 8
  • 22
  • 2
    If you're concerned with speed, don't use multidimensional arrays. They're very slow in .NET (compared to single dimensional arrays anyway). Even jagged arrays are faster, despite the pointer chasing. The simplest+fastest thing to do here is make it 1-dimensional and do the indexing math yourself. – harold Feb 29 '12 at 09:48
  • this is somehow related and found it interesting: http://stackoverflow.com/questions/136836/c-sharp-array-initialization-with-non-default-value – mindandmedia Feb 29 '12 at 09:51
  • Thanks for your comments, speed is actual not an issue in my current application but I'd prefer a clean syntax here. – Florian Storck Feb 29 '12 at 09:56
  • What do you mean by own classes vs. value types? You can write your own constructor for a `struct`, which is a value type, too. Sorry if I'm misunderstanding. – Matthias Meid Feb 29 '12 at 12:59
  • well, yes, but I meant basic types like double, int etc. – Florian Storck Feb 29 '12 at 13:51

3 Answers3

3

Not quite sure if it's what you want, but the following extension method will allow you to initialise every value in an array, regardless of the number of dimensions.

public static class ArrayExtensions
    {
        public static void Set<T>(this Array array, T defaultValue)
        {
            int[] indicies = new int[array.Rank];

            SetDimension<T>(array, indicies, 0, defaultValue);
        }

        private static void SetDimension<T>(Array array, int[] indicies, int dimension, T defaultValue)
        {
            for (int i = 0; i <= array.GetUpperBound(dimension); i++)
            {
                indicies[dimension] = i;

                if (dimension < array.Rank - 1)
                    SetDimension<T>(array, indicies, dimension + 1, defaultValue);
                else
                    array.SetValue(defaultValue, indicies);
            }
        }
    }

Use like so:

int[, ,] test1 = new int[3, 4, 5];
test1.Set(1);

int[,] test2 = new int[3, 4];
test2.Set(1);

int[] test3 = new int[3];
test3.Set(1);
Andy Holt
  • 572
  • 2
  • 9
  • thank's, yes, exactly, that was my idea. It looks rather complicated, but it seems there is no really .net builtin solution for that problem. – Florian Storck Feb 29 '12 at 13:54
2

Here is a non-recursive version alternative to that posted by Andy Holt above:

    public static void SetAll<T>(this Array array, T value)
    {
        var sizes = new int[array.Rank];

        sizes[array.Rank - 1] = 1;
        for (var d = array.Rank - 2; d >= 0; d--)
        {
            sizes[d] = array.GetLength(d + 1)*sizes[d + 1];
        }

        for (var i = 0; i < array.Length; i++)
        {
            var remainder = i;
            var index = new int[array.Rank];
            for (var d = 0; d < array.Rank && remainder > 0; d++)
            {
                index[d] = remainder / sizes[d];
                remainder -= index[d]*sizes[d];
            }
            array.SetValue(value, index);
        }
    }
2

I would strongly recommend using a 1D array, and map the values sequentially. You will need to convert from indeces i, j, k, ... into the proper array index, which is done with the Que() function below, part of a generic array class SeqArray<T>.

// Test code first
class Program
{
    static void Main(string[] args)
    {
        /* 3 pages, of a 4x2 matrix
         * 
         *         |16 17|
         *      | 8  9|19|
         *   | 0  1|11|21|
         *   | 2  3|13|23|
         *   | 4  5|15|
         *   | 6  7|
         *   
         *  shown above are the sequential indeces for a rank 3 array
         */
        SeqArray<double> arr = new SeqArray<double>(3, 4, 2);
        // Initialize values to squential index "num"
        int num = 0;
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                for (int k = 0; k < 2; k++)
                {
                    arr[i, j, k] = num++;
                }
            }
        }
        // Check that the array values correspond to the index sequence
        num = 0;
        for (int i = 0; i < 3 * 4 * 2; i++)
        {
            Trace.Assert(arr.InnerArray[i] == num++);
        }

        // Initialize with value=π
        arr = new SeqArray<double>(Math.PI, 4, 5, 6);
    }

}

public class SeqArray<T>
{
    T[] values;
    int[] lengths;

    public SeqArray(params int[] lengths)
    {
        this.lengths = lengths;
        int N = 1;
        for (int i = 0; i < lengths.Length; i++)
        {
            N *= lengths[i];
        }
        values = new T[N];
    }
    public SeqArray(T value, params int[] lengths) : this(lengths)
    {
        for (int i = 0; i < values.Length; i++)
        {
            values[i] = value;
        }            
    }
    public int[] Lengths { get { return lengths; } }
    public int Size { get { return values.Length; } }
    internal T[] InnerArray { get { return values; } }
    public int Que(params int[] indeces)
    {
        // Check if indeces are omited like arr[4] instead of arr[4,0,0]
        if (indeces.Length < lengths.Length)
        {
            // Make a new index array padded with zeros
            int[] temp = new int[lengths.Length];
            indeces.CopyTo(temp, 0);
            indeces = temp;
        }
        // Count the elements for indeces
        int k = 0;
        for (int i = 0; i < indeces.Length; i++)
        {
            k = lengths[i] * k + indeces[i];
        }
        return k;
    }

    public T this[params int[] indeces]
    {
        get { return values[Que(indeces)]; }
        set { values[Que(indeces)] = value; }
    }
}
John Alexiou
  • 28,472
  • 11
  • 77
  • 133
  • You will have to create child classes for different numeric types in order to use the `+`, `-` operator overloading. To code a matrix multiplication would be a little work, but doable. – John Alexiou Feb 29 '12 at 15:35