我有一个二维字节数组,如下所示:
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);
}
所以,我的问题是:
答案 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 想要在添加之前检查这些值,但是这里是不必要的。