多维数组的通用初始化方法

3

我有一个多维数组,希望能够简单快速地初始化:

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

这显然行不通。但我希望有一个通用的函数,可以将所有数组值设置为所选默认值。对于自己的类,我可以编写特殊的构造函数,但对于值类型,我没有真正的主意。在C++中,我可以使用一个for循环以线性方式访问所有项,但在C#中,我认为我必须使用与维度一样多的for循环。目前我没有更好的解决方案(或者我正在使用不安全的代码和指针算术,这可能会起作用)。
有更优雅的方法吗?

2
如果你关心速度,不要使用多维数组。在.NET中它们非常慢(无论与单维数组相比如何)。即使是交错数组,速度也更快,尽管需要指针追踪。在这里最简单和最快的方法是将其变为一维数组,并自己进行索引计算。 - harold
这与编程有关,我觉得很有趣:https://dev59.com/LnVC5IYBdhLWcg3w9GE9 - mindandmedia
感谢您的评论,速度在我的当前应用程序中实际上并不是问题,但我更喜欢这里的清晰语法。 - Florian Storck
什么是自有类和值类型?你可以为struct编写自己的构造函数,这也是一种值类型。如果我有什么误解,请见谅。 - Matthias Meid
好的,是的,但我指的是像double、int等基本类型。 - Florian Storck
3个回答

3

我不确定这是否符合你的要求,但是以下扩展方法将允许您初始化数组中的每个值,无论维数如何。

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);
            }
        }
    }

使用方式如下:

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);

谢谢,是的,没错,那正是我的想法。看起来相当复杂,但似乎没有真正的 .net 内置解决方案来解决这个问题。 - Florian Storck

2
这里提供一种非递归版本的解决方案,与Andy Holt上面发布的解决方案不同:
    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
我强烈建议使用一维数组,并按顺序映射值。您需要将索引ijk等转换为正确的数组索引,这可以通过下面的Que()函数完成,该函数是通用数组类SeqArray<T>的一部分。请注意保留HTML标签。
// 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接