如何使用具有二维数组的LINQ

时间:2015-01-26 02:30:57

标签: c# arrays linq plinq

我有一个二维字节数组,如下所示:

  

0 0 0 0 1

     

1 1 1 1 0

     

0 0 1 1 1

     

1 0 1 0 1

数组中的每个值只能是0或1.上面的简化示例显示了4行,每行有5列。我试图弄清楚如何使用LINQ将索引返回到具有最大数量1s的行,在上面的示例中应该返回1.

以下非LINQ C#代码解决了这个问题:

static int GetMaxIndex(byte[,] TwoDArray)
{
   // This method finds the row with the greatest number of 1s set.
   //
   int NumRows = TwoDArray.GetLength(0);
   int NumCols = TwoDArray.GetLength(1);
   int RowCount, MaxRowCount = 0, MaxRowIndex = 0;
   //
   for (int LoopR = 0; LoopR < NumRows; LoopR++)
   {
      RowCount = 0;
      for (int LoopC = 0; LoopC < NumCols; LoopC++)
      {
         if (TwoDArray[LoopR, LoopC] != 0)
            RowCount++;
      }
      if (RowCount > MaxRowCount)
      {
         MaxRowCount = RowCount;
         MaxRowIndex = LoopR;
      }
   }
   return MaxRowIndex;
}

static void Main()
{
   byte[,] Array2D = new byte[4, 5] { { 0, 0, 0, 0, 1 }, { 1, 1, 1, 1, 0 }, { 0, 0, 1, 1, 1 }, { 1, 0, 1, 0, 1 } };
   int MaxInd = GetMaxIndex(Array2D);
   Console.WriteLine("MaxInd = {0}", MaxInd);
}

所以,我的问题是:

  1. 如何使用LINQ来解决这个问题,并且在这里使用LINQ会比使用上面的非LINQ代码效率低吗?
  2. PLINQ可以解决这个问题吗?或者,将任务并行库(TPL)直接用于上述代码并将每行中1的数量计数拆分为单独的线程会更高效,假设每行至少有1,000列? / LI>

5 个答案:

答案 0 :(得分:5)

使用LINQ很难处理多维数组,但是你可以这样做:

var arr = new [,] { { 0, 0, 0, 0, 1 }, { 1, 1, 1, 1, 0 }, { 0, 0, 1, 1, 1 }, { 1, 0, 1, 0, 1 } };

var data =
    Enumerable.Range(0, 4)
        .Select(
            row =>
                new
                {
                    index = row,
                    count = Enumerable.Range(0, 5).Select(col => arr[row, col]).Count(x => x == 1)
                })
        .OrderByDescending(x => x.count)
        .Select(x => x.index)
        .First();

答案 1 :(得分:1)

1)你可以用LINQ这样做......

private static int GetMaxIndex(byte[,] TwoDArray) {
    return Enumerable.Range(0, TwoDArray.GetLength(0))
                     .Select(
                         x => new {
                             Index = x,
                             Count = Enumerable.Range(0, TwoDArray.GetLength(1)).Count(y => TwoDArray[x, y] == 1)
                         })
                     .OrderByDescending(x => x.Count)
                     .First()
                     .Index;
}

...你必须测试它以查看LINQ是更快还是更慢。

2)可以使用PLINQ。只需使用ParallelEnumerable.Range作为行索引生成器

private static int GetMaxIndex2(byte[,] TwoDArray) {
    return ParallelEnumerable.Range(0, TwoDArray.GetLength(0))
                             .Select(
                                 x => new {
                                     Index = x,
                                     Count = Enumerable.Range(0, TwoDArray.GetLength(1)).Count(y => TwoDArray[x, y] == 1)
                                 })
                             .OrderByDescending(x => x.Count)
                             .First()
                             .Index;
}

答案 2 :(得分:1)

我将如何做到这一点。它或多或少与其他人一样,但是没有任何Enumerable.Range(并不是说它们有任何问题(我一直使用它们)......它只会使代码在这种情况下更加缩进)。< / p>

这个还包括PLINQ的东西。 TPL(async / await)不适用于此,因为它受计算限制,TPL更适合I / O绑定操作。如果使用async / await而不是PLINQ,则代码将最终按顺序执行。这是因为async / await在线程被释放之前不会并行(并且它可以启动下一个任务......然后可以并行执行)并且纯粹的同步函数(例如CPU内容)并不会真正等待。 ..他们会一路奔跑。基本上,它会在你开始下一件事之前完成列表中的第一件事,使它按顺序执行。 PLINQ显式启动并行任务,没有这个问题。

//arry is your 2d byte array (byte[,] arry)
var maxIndex = arry
    .Cast<byte>() //cast the entire array into bytes
    .AsParallel() //make the transition to PLINQ (remove this to not use it)
    .Select((b, i) => new // create indexes
        {
            value = b,
            index = i
        })
    .GroupBy(g => g.index / arry.GetLength(1)) // group it by rows
    .Select((g, i) => new
        {
            sum = g.Select(g2 => (int)g2.value).Sum(), //sum each row
            index = i
        })
    .OrderByDescending(g => g.sum) //max by sum
    .Select(g => g.index) //grab the index
    .First(); //this should be the highest index

就效率而言,使用for循环可能会获得更好的效果。我要问的问题是,哪个更具可读性和清晰度?

答案 3 :(得分:0)

// This code is extracted from
// http://www.codeproject.com/Articles/170662/Using-LINQ-and-Extension-Methods-in-C-to-Sort-Vect
private static IEnumerable<T[]> ConvertToSingleDimension<T>(T[,] source)
{
    T[] arRow;
    for (int row = 0; row < source.GetLength(0); ++row)
    {
        arRow = new T[source.GetLength(1)];
        for (int col = 0; col < source.GetLength(1); ++col)
            arRow[col] = source[row, col];
        yield return arRow;
    }
}


// Convert byte[,] to anonymous type {int index, IEnumerable<byte[]>} for linq operation
var result = (from item in ConvertToSingleDimension(Array2D).Select((i, index) => new {Values = i, Index = index})
             orderby item.Values.Sum(i => i) descending, item.Index
             select item.Index).FirstOrDefault();

答案 4 :(得分:0)

看着这个问题,对于代码的“更有效”的问题,这实际上是一个两部分的答案。呈现的循环已经非常依赖资源,但是在意图上可能更加清晰。

基于要移动的数据大小,即使是原来的10倍,PLINQ也会占用更多资源,这只是因为启动一个线程需要进行大量工作。

1。)使用LINQ可使该方法更具可读性

我遇到的大多数2D数组LINQ查询在搜索之前都会将其转换为锯齿状数组(或数组数组)。这是一个帮助我们完成转换的助手方法,可以帮助使这个人看起来更干净:

public static T[][] GetJagged<T>(this T[,] raw)
    {
        int lenX = raw.GetLength(0);
        int lenY = raw.GetLength(1);

        T[][] jagged = new T[lenX][];

        for (int x = 0; x < lenX; x++)
        {
            jagged[x] = new T[lenY];
            for (int y = 0; y < lenY; y++)
            {
                jagged[x][y] = raw[x, y];
            }
        }

        return jagged;
    }

现在,剩下的就是为每个成员查询现在的1d数组,并返回每个成员的总和。在这里,我使用选择器(b => b),实际上是说如果有一个字节,请为Sum方法选择是否。

static int GetMaxIndexLINQ(byte[,] TwoDArray)
    {
        byte[][] jagged = TwoDArray.GetJagged();

        IEnumerable<int> rowSums = from bitRows in jagged
                                   select bitRows.Sum((b) => b);

        int maxIndex = rowSums.Max();
        int MaxRowIndex = Array.IndexOf(rowSums.ToArray(), maxIndex);
        return MaxRowIndex;
    }

这种方式非常清晰易懂,即使读者不熟悉编码,也很容易了解这里发生的事情。

我想指出的是,使您的代码更具可读性 是使代码更高效。团队合作使梦想成真,而团队成员越快就能清楚地了解代码中发生的事情,对每个人都越好。

2。)优化性能

正如我之前说的,这里没有任何事情可以使它变得更精简,任何方法调用或不必要的检查都只会使此过程变慢。

话虽这么说,但要进行一些简单的优化就需要做一些小的改动。因为在这种情况下,我们只处理1和0,所以有一个真正的好处,那就是我们可以使用编译器进行的内部优化来使我们受益。与其检查值是否为0,不如将其添加到我们的总和中实际上要快得多!

static int GetMaxIndex_EvenBetter(byte[,] TwoDArray)
    {
        int NumRows = TwoDArray.GetLength(0);
        int NumCols = TwoDArray.GetLength(1);
        int RowCount, MaxRowCount = 0, MaxRowIndex = 0;

        for (int row = 0; row < NumRows; row++)
        {
            RowCount = 0;

            for (int col = 0; col < NumCols; col++)
            {
                RowCount += TwoDArray[row, col]; //See my change here
            }
            if (RowCount > MaxRowCount)
            {
                MaxRowCount = RowCount;
                MaxRowIndex = row;
            }
        }

        return MaxRowIndex;
    }

在大多数其他情况下,您不仅需要使用1和0,因此 DO 想要在添加之前检查这些值,但是这里是不必要的。