如何在C#中从二维数组中获取完整的行或列

68

我不想使用锯齿数组,我有一个二维数组,我想要获取一个完整的列或行而不通过循环遍历它。有人有办法知道如何做到吗。

// 1   2   3
// 4   5   6
double [,]  array = new double [2,3];

Out: 1   2   3  or 2   5 

https://dev59.com/i14c5IYBdhLWcg3w3tkv#51241629 应该是被接受的答案。 - Soleil
不应该这样做。虽然代码非常简洁,但实际上并没有回答关于不循环的问题,LINQ 在幕后自然地完成了这个任务。 - Paul Childs
8个回答

109

要从多维数组中获取特定的行或列,您可以使用一些LINQ:

public class CustomArray<T>
{
    public T[] GetColumn(T[,] matrix, int columnNumber)
    {
        return Enumerable.Range(0, matrix.GetLength(0))
                .Select(x => matrix[x, columnNumber])
                .ToArray();
    }

    public T[] GetRow(T[,] matrix, int rowNumber)
    {
        return Enumerable.Range(0, matrix.GetLength(1))
                .Select(x => matrix[rowNumber, x])
                .ToArray();
    }
}

1
与被采纳的答案不同,这适用于bool类型以及非基本类型。 - BCA
2
你的概念混淆了吗?代码不应该是:matrix[columnNumber, x] 和 matrix[x, rowNumber] 吗?大多数人在概念上认为一维数组是单个“行”,有多列,而二维数组则有多行。毕竟,列数是X轴,行数是Y轴。 - bytedev
4
这应该是被接受的答案——它通用、优雅,并且允许查询而不需要枚举整个结果列表。 - MandisaW
@bytedev 我认为这是因为在数学中,矩阵的索引方式是_[行,列]_,因此保持一致是有意义的。此外,在其他语言中经常使用嵌套数组(数组的数组)来模拟二维数组,外部数组(首先索引)通常被认为包含行,每个内部数组(第二个索引)被认为是单个行(可能因为它们在代码中的定义方式相对应 - 可能有一行代码来定义每行),如果这样说的话。 - Ruben9922
9
私语:使用扩展方法,而不是新类。 - ruffin
显示剩余4条评论

25

你可以使用 Buffer.BlockCopy() 优化它以获得行,但要获取列,则必须循环。 Buffer.BlockCopy() 最终使用处理器指令来复制一块内存,因此速度非常快。

将代码放入扩展方法中以使调用更加方便。请注意,Buffer.BlockCopy() 只能用于原始类型的数组,即 intdoublechar 等。这不包括 string

以下是一个可编译的示例:

using System;
using System.Linq;
using System.Runtime.InteropServices;

namespace ConsoleApplication4
{
    public static class Program
    {
        private static void Main()
        {
            var array = new [,]
            {
                {0.1, 0.2, 0.3, 0.4, 0.5},
                {1.1, 1.2, 1.3, 1.4, 1.5},
                {2.1, 2.2, 2.3, 2.4, 2.5},
                {3.1, 3.2, 3.3, 3.4, 3.5},
            };

            var row = array.GetRow(2);

            // This prints 2.1, 2.2, 2.3, 2.4, 2.5

            Console.WriteLine(string.Join(", ", row.Select(element => element.ToString())));
        }
    }

    public static class ArrayExt
    {
        public static T[] GetRow<T>(this T[,] array, int row)
        {
            if (!typeof(T).IsPrimitive)
                throw new InvalidOperationException("Not supported for managed types.");

            if (array == null)
                throw new ArgumentNullException("array");

            int cols = array.GetUpperBound(1) + 1;
            T[] result = new T[cols];

            int size;

            if (typeof(T) == typeof(bool))
                size = 1;
            else if (typeof(T) == typeof(char))
                size = 2;
            else
                size = Marshal.SizeOf<T>();

            Buffer.BlockCopy(array, row*cols*size, result, 0, cols*size);

            return result;
        }
   }
}

1
请注意,这不适用于基本类型“bool”。 - BCA
2
@BCA 是的,因为 Marshal.SizeOf(bool) 的值是4,而不是1。我已经通过显式检查该类型并在布尔值时使用大小为1来修复了这个问题。它也无法处理 char,所以我也加入了一个修复程序。 - Matthew Watson

8

从2021年3月份开始,您现在可以使用非常酷的Span2D类来实现这一点!

如果您喜欢使用spans(我强烈建议您阅读有关它们的内容,它们非常棒),您可以使用以下代码:

var span2D = new Span2D<double>(array);

//Gets the first row and returns a span of it
var rowSpan = span2D.GetRowSpan(0);

foreach(var number in rowSpan)
{
    //Do something with numbers
}

//Gets the 2nd Column as a RefEnumerable and converts it to an array
var column = span2D.GetColumn(1).ToArray();

1
请点击此处查看NuGet包的文档。但需要注意的是,它已被弃用,推荐使用CommunityToolkit.HighPerformance - Andrew Matthews

0

这是我如何完成它的方法,你可以使用

GetLength(0)

来获取列数,并使用

GetLength(1)

来获取二维数组的行数,如果有其他人需要,你可以通过for循环遍历它。

string text = "";
for (int i = 0; i < array.GetLength(0); i++)
{
   text += Convert.ToString(array[i, 2]) + "\n";
}

3
如果你要在循环中追加字符串,建议使用C#中的StringBuilder而不是普通的string。 - Thymine

0
需要的是一个锯齿数组 (而不是多维数组)。

https://msdn.microsoft.com/en-us/library/2s05feca.aspx

int[][] jaggedArray = new int[3][];
jaggedArray[0] = new int[5];
jaggedArray[1] = new int[] { 0, 2, 4, 6 };
jaggedArray[2] = new int[] { 11, 22 };

带列的完整示例:

using System;
using System.Collections.Generic;

namespace Rextester
{
    public class Program
    {

        public static T[] column<T>(T[][] jaggedArray,int wanted_column)
        {
            T[] columnArray = new T[jaggedArray.Length];
            T[] rowArray;
            for(int i=0;i<jaggedArray.Length;i++)
            {
                rowArray=jaggedArray[i];
                if(wanted_column<rowArray.Length)
                    columnArray[i]=rowArray[wanted_column];
            }
            return columnArray;
        }

        public static void Main(string[] args)
        {
            //Your code goes here
                int[][] jaggedArray = new int[3][];
                jaggedArray[0] = new int[5];
                jaggedArray[1] = new int[] { 0, 2, 4, 6 };
                jaggedArray[2] = new int[] { 11, 22 };

                Console.WriteLine("Hello, world!");
                Console.WriteLine(string.Join(" ",jaggedArray[1]));
                Console.WriteLine(string.Join(" ",column(jaggedArray,1)));
        }
    }
}

类似的想法,使用扩展:

using System;
using System.Collections.Generic;

namespace Rextester
{
    public static class MyExtensions
    {
        public static string Extend(this Array array)
        {
            return "Yes, you can extend an array";
        }

        public static T[] column<T>(this T[,] multidimArray,int wanted_column)
        {
            int l=multidimArray.GetLength(0);
            T[] columnArray = new T[l];
            for(int i=0;i<l;i++)
            {
              columnArray[i]=multidimArray[i,wanted_column];
            }
            return columnArray;
        }

        public static T[] row<T>(this T[,] multidimArray,int wanted_row)
        {
            int l=multidimArray.GetLength(1);
            T[] rowArray = new T[l];
            for(int i=0;i<l;i++)
            {
              rowArray[i]=multidimArray[wanted_row,i];
            }
            return rowArray;
        }


    } 

    public class Program
    {


        public static void Main(string[] args)
        {
                Console.WriteLine("Hello, world!");

                int [,] multidimArray = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
                Console.WriteLine(string.Join(" ",multidimArray.column(0)));
                Console.WriteLine(string.Join(" ",multidimArray.row(0)));

        }
    }
}

1
这并不能帮助获取完整的列,只是帮助行。 - David Burg
没有什么奇迹。就像Behrooz在这里说的一样...你可以填两次,一次为行,一次为列。或者使用一些访问器Behrooz所说的,或者使用一个使用访问器并将其复制到数组并返回数组的函数。 - Shimon Doodkin
1
对于锯齿数组来说,“列”的概念完全没有任何意义。 - Paul Childs

0

你可以使用List而不是数组来实现另一种方式。

具体来说,在你的情况下,你可以这样做:

  1. 最初创建一个表示数组元组的内部类
  2. 创建一个内部类的List
  3. 填充内部类
  4. 获取包含特定内容的行
  5. 获取包含特定内容的列
public static void Main(string[] args)
{
    // #2 -- Instantiate List of myClass
    List<myClass> myList = new List<myClass>();
    //
    // #3 -- Populate the list
    myList.Add(new myClass(1,2,3));           
    myList.Add(new myClass(3,4,5));
    myList.Add(new myClass(5,6,6));
    //
    // #4 -- Get the line where a == 1
    myList.Find(x=>x.a == 1);
    //
    // #5 -- Get column b
    myList.Select(x=>x.b);
}
// #1 -- Create the inner class
public class myClass
{
    public int a;
    public int b;
    public int c;
    public myClass(int a, int b, int c)
    {
        this.a =a;
        this.b =b;
        this.c =c;
    }
}

这不是一个锯齿数组吗?最好使用内置类型。选择方法正在循环。 - David Burg

-1

我的用例与问题不同,但类似。我需要一个由float[2]数组组成的二维数组,用于表示复数。

float[,,] myarray = new float[100,100,2];
float[] result = myarray[1,1];   <-- fails to compile needs all 3 coordinates

Simmon提到的交错数组提供了解决方案。

float[,][] myarray = new float[100,100][];
...
myarray[x,y] = new float[2];  <-- Initialise all elements of jagged 2D array in loop
...
float[] result = [100,100];

-6
如果您知道要输出的数字的索引,则不需要使用循环来获取所需的输出...
double[,] array = new double[3,3] {{1,2,3}, {4,5,6}, {7,8,9}}; 

int firstNum = array[0,1];
int secondNum = array[1,1];

这将得到2、5


2
问题中的示例是使用一个小数组简化的。问题的精神是针对任意维度的数组进行操作。列出每个循环中的命令并不能回答如何在没有循环的情况下实现目标的问题。 - David Burg

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