如何将数组列表转换为多维数组

22

我需要将以下集合转换为double[,]:

 var ret = new List<double[]>();

列表中的所有数组长度相同。最简单的方法是使用ret.ToArray(),但它会生成double[][],这不是我想要的。当然,我可以手动创建一个新数组,并在循环中复制数字,但是否有更优雅的方法?

编辑:我的库是从另一种语言Mathematica调用的,它没有在.Net中开发。我不认为该语言可以使用交错数组。我必须返回一个多维数组。


1
听起来你想要一个“锯齿状”的数组而不是一个二维数组——你确定这是你的需求吗?因为所有的数组长度都是相同的。 - BrokenGlass
1
最好修改需要多维数组的代码,这样会更加优雅。 - Jodrell
也许你应该发布一份代码,准确地解释你想要什么,并解释为什么你希望它是这样的。 - Bernard
仅仅复制数据有什么问题吗?最终需要改变结构,这是必须的。而且双重循环并不复杂,我认为没有必要花时间和精力将其转换成其他形式。如果你经常这样做,可以编写一个实用方法来进行转换。 - Servy
1
如果可以的话,我建议将输出保留为锯齿数组。在C#中,多维数组和数组数组有什么区别? - David
5个回答

31

我不认为框架内置了任何功能来实现这个,甚至Array.Copy在这种情况下也无法实现。不过,通过循环编写代码很容易实现:

using System;
using System.Collections.Generic;

class Test
{
    static void Main()
    {
        List<int[]> list = new List<int[]>
        {
            new[] { 1, 2, 3 },
            new[] { 4, 5, 6 },
        };

        int[,] array = CreateRectangularArray(list);
        foreach (int x in array)
        {
            Console.WriteLine(x); // 1, 2, 3, 4, 5, 6
        }
        Console.WriteLine(array[1, 2]); // 6
    }

    static T[,] CreateRectangularArray<T>(IList<T[]> arrays)
    {
        // TODO: Validation and special-casing for arrays.Count == 0
        int minorLength = arrays[0].Length;
        T[,] ret = new T[arrays.Count, minorLength];
        for (int i = 0; i < arrays.Count; i++)
        {
            var array = arrays[i];
            if (array.Length != minorLength)
            {
                throw new ArgumentException
                    ("All arrays must be the same length");
            }
            for (int j = 0; j < minorLength; j++)
            {
                ret[i, j] = array[j];
            }
        }
        return ret;
    }

}

谢谢Jon!我是你书的忠实粉丝。虽然我的代码看起来很相似,但是你的代码更加可重用。 - Arne Lund
我不相信框架内置了任何东西来完成这个任务 - 这是答案中最有用的部分。点赞 :) - Alex Fainshtein

6

您可以将以下内容作为扩展进行:

    /// <summary>
    /// Conerts source to 2D array.
    /// </summary>
    /// <typeparam name="T">
    /// The type of item that must exist in the source.
    /// </typeparam>
    /// <param name="source">
    /// The source to convert.
    /// </param>
    /// <exception cref="ArgumentNullException">
    /// Thrown if source is null.
    /// </exception>
    /// <returns>
    /// The 2D array of source items.
    /// </returns>
    public static T[,] To2DArray<T>(this IList<IList<T>> source)
    {
        if (source == null)
        {
            throw new ArgumentNullException("source");
        }

        int max = source.Select(l => l).Max(l => l.Count());

        var result = new T[source.Count, max];

        for (int i = 0; i < source.Count; i++)
        {
            for (int j = 0; j < source[i].Count(); j++)
            {
                result[i, j] = source[i][j];
            }
        }

        return result;
    }

如果我为一个广泛使用的东西创建一个扩展方法,那会减慢我的构建速度,对吗? - Arne Lund
2
不,这只是普通方法。 - Marcin

5
如果您要复制(我想不出更好的方法)
var width = ret[0].length;
var length = ret.Count;
var newResult = new double[width, length]
Buffer.BlockCopy(ret.SelectMany(r => r).ToArray(),
                    0, 
                    newResult, 
                    0, 
                    length * width);
return newResult;

编辑

我相信使用循环而不是使用SelectManyToArray更快。

我知道当我被抛弃时的感觉。


5

由于您所描述的情况中,列表中的double[]数组大小不尽相同,这将与二维矩形数组不兼容,因此没有简单的方法可以解决该问题。但是,如果您能够保证所有的double[]数组具有相同的维度,您可以按如下方式构建您的二维数组:

var arr = new double[ret.Count(),ret[0].Count()];

for( int i=0; i<ret.Count(); i++ ) {
  for( int j=0; j<ret[i].Count(); j++ )
    arr[i,j] = ret[i][j];
}

这将在列表中出现任何一个double[]数组短于第一个时产生运行时错误,如果有任何一个数组比第一个大,则会失去数据。
如果您真的决定将不规则数组存储在矩形数组中,您可以使用“魔术”值来表示该位置没有值。例如:
var arr = new double[ret.Count(),ret.Max(x=>x.Count())];

for( int i=0; i<ret.Count(); i++ ) {
  for( int j=0; j<arr.GetLength(1); j++ )
    arr[i,j] = j<ret[i].Count() ? ret[i][j] : Double.NaN;
}

在编辑说明中,我认为这是一个非常糟糕的想法;当您使用矩形阵列时,您必须一直检查Double.NaN。此外,如果您想在数组中使用Double.NaN作为合法值怎么办?如果您有一个锯齿形阵列,您应该将其保留为锯齿形阵列。


是的,列表中的double[]数组可以具有不同的大小。我知道所有的数组都具有相同的大小,但是.Net并非如此。 - Arne Lund

0

我正在寻找相反的需求,我需要将一个二维数组转换为数组的数组。我需要这样做是因为JSON不支持多维数组的序列化。

/// <summary>
/// converts a 2 dimensional array to an array of arrays
/// </summary>
/// <typeparam name="T">type of arays</typeparam>
/// <param name="src">2-dimensional array</param>
/// <returns>array of arrays of the same size the <paramref name="src"/></returns>
public static T[][] Convert2DArray<T>(this T[,] src)
{
    // match input
    if (src == null)
        return null;

    // get array dimensions
    var height = src.GetLength(0);
    var width = src.GetLength(1);

    // create the new array
    var tgt = new T[height][];
    for (int i = 0; i < height; i++)
    {
        tgt[i] = new T[width];
        for (int j = 0; j < width; j++)
            tgt[i][j] = src[i, j];
    }

    // return it
    return tgt;
}

这是一个三维版本:

/// <summary>
/// converts a 3 dimensional array to an array of arrays
/// </summary>
/// <typeparam name="T">type of arays</typeparam>
/// <param name="src">3-dimensional array</param>
/// <returns>array of arrays of the same size the <paramref name="src"/></returns>
public static TDest[][][] Convert3DArray<TSrc, TDest>(this TSrc[,,] src, Func<TSrc, TDest> converter)
{
    // match input
    if (src == null)
        return null;
    if (converter is null)
        throw new ArgumentNullException(nameof(converter));

    // get array dimensions
    var iLen = src.GetLength(0);
    var jLen = src.GetLength(1);
    var kLen = src.GetLength(2);

    // create the new array
    var tgt = new TDest[iLen][][];
    for (int i = 0; i < iLen; i++)
    {
        tgt[i] = new TDest[jLen][];
        for (int j = 0; j < jLen; j++)
        {
            tgt[i][j] = new TDest[kLen];
            for (int k = 0; k < kLen; k++)
                tgt[i][j][k] = converter(src[i, j, k]);
        }
    }
    // return it
    return tgt;
}

而且为了更有趣,这个函数可以同时用于删除一个维度(3D->2D)

/// <summary>
/// converts a 3 dimensional array to an array of arrays (2D)
/// </summary>
/// <typeparam name="T">type of arays</typeparam>
/// <param name="src">3-dimensional array</param>
/// <param name="converter">a function to convert the 3rd dimension into a new type (e.g. concatenated string)</param>
/// <returns>array of arrays of the same size the <paramref name="src"/></returns>
public static TDest[][] Convert3DTo2DArray<TSrc, TDest>(this TSrc[,,] src, Func<IEnumerable<TSrc>, TDest> converter)
{
    // match input
    if (src == null)
        return null;
    if (converter is null)
        throw new ArgumentNullException(nameof(converter));

    // get array dimensions
    var iLen = src.GetLength(0);
    var jLen = src.GetLength(1);
    var kLen = src.GetLength(2);

    // create the new array
    var tgt = new TDest[iLen][];
    for (int i = 0; i < iLen; i++)
    {
        tgt[i] = new TDest[jLen];
        for (int j = 0; j < jLen; j++)
        {
            tgt[i][j] = converter(GetEnumerableFor3rdDimension(src, i, j, kLen));
        }
    }
    // return it
    return tgt;
}

private static IEnumerable<T> GetEnumerableFor3rdDimension<T>(T[,,] src, int i, int j, int kLen)
{
    for (int k = 0; k < kLen; k++)
        yield return src[i, j, k];
}

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