使用 LINQ 通过一个查询从数组中获取前五个元素和后五个元素
Posted
技术标签:
【中文标题】使用 LINQ 通过一个查询从数组中获取前五个元素和后五个元素【英文标题】:Take the first five elements and the last five elements from an array by one query using LINQ 【发布时间】:2015-10-21 12:05:56 【问题描述】:最近一位同事问我:是否可以通过一次查询从数组中获取前五个元素和后五个元素?
int[] someArray = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 ;
我尝试过的:
int[] someArray = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 ;
var firstFiveResults = someArray.Take(5);
var lastFiveResults = someArray.Skip(someArray.Count() - 5).Take(5);
var result = firstFiveResults;
result = result.Concat(lastFiveResults);
一个查询是否可以只取前五个元素和后五个元素?
【问题讨论】:
你试过了吗 var result = someArray.Take(5).Union(someArray.Skip(someArray.Count() - 5).Take(5)); ?someArray.Take(5).Concat(someArray.Reverse().Take(5));
怎么样?
@MitatKoyuncu, Union
删除可能是也可能不是您想要的重复条目。如果你确实想要输出 10 个结果,请使用 Concat
@StepUp,同样,如果someArray
的输入少于 10 个或少于 5 个怎么办。您的问题不够具体,无法提供一个好的、可靠的答案。
@StepUp,没有什么是愚蠢的问题 :) 我的意思是你想要为你创建的方法的参数是什么类型的?例如,它可以是int[]
或T[]
(如果是通用的),但是它也可以根据集合类常用的几个接口之一来定义。例如,大部分 linq 是在 IEnumerable<T>
上定义的,这是非常通用的。您也可以在 IReadOnlyList<T>
、IList<T>
、ICollection<T>
上定义它...您选择的“类型”决定了某人可以传递给该方法的有效集合,它还限制了实现可以使用的技术。
【参考方案1】:
您可以使用带有 lambda 的 .Where
方法,该方法接受元素索引作为其第二个参数:
int[] someArray = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 ;
int[] newArray = someArray.Where((e, i) => i < 5 || i >= someArray.Length - 5).ToArray();
foreach (var item in newArray)
Console.WriteLine(item);
输出:
0, 1, 2, 3, 4, 14, 15, 16, 17, 18
【讨论】:
如果输入序列少于 5 个项目,此答案是唯一不会抛出的答案之一。但是 OP 从未为这种情况指定行为——即重叠的头部和尾部是否应该在输出中重复? 如果someArray
的长度在 5..9 区间内,此解决方案的结果与我的解决方案不同(如下)。提问者能否保证数组总是足够长?在数组长度小于 5 的情况下,我的解决方案会抛出异常,而上述解决方案会产生一次所有条目。
只是一个编码的东西,但我更喜欢(_, i) => i < 5 || i >= someArray.Length - 5
。 _
表示未使用该变量。 >=
允许 5
对应于从列表末尾获取的元素数。
Fabjan,这并不是要批评您的回答。正如@Discosultan 所说,您的解决方案(在数组长度低于 10 的情况下)可能是一个自然的解决方案。我只是想提一下,当数组的条目少于 10 个时,对于“正确”的内容有几种可能的解释。如果数组非常长,比如 100,000,000 个条目,您的方法将不得不多次评估 Func<,,>
passed 到 Where
。在这种情况下,我的解决方案会更快。由于这可能是微优化,因此可能完全无关紧要。【参考方案2】:
ArraySegment<>
的解决方案(需要 .NET 4.5 (2012) 或更高版本):
var result = new ArraySegment<int>(someArray, 0, 5)
.Concat(new ArraySegment<int>(someArray, someArray.Length - 5, 5));
还有Enumerable.Range
的解决方案:
var result = Enumerable.Range(0, 5).Concat(Enumerable.Range(someArray.Length - 5, 5))
.Select(idx => someArray[idx]);
这两种解决方案都避免遍历数组的“中间”(索引 5 到 13)。
【讨论】:
【参考方案3】:如果您不是和同事一起玩代码拼图,而只是想根据您的条件创建一个新数组,我根本不会使用查询来执行此操作,而是使用 Array.copy。
需要考虑三种不同的情况:
源数组的项少于 5 个 源数组有 5 到 9 个项目 源数组有 10 个或更多项第三种是简单的情况,因为第一个和最后 5 个元素是不同的且定义明确的。
另外两个需要更多的思考。我将假设你想要以下,检查这些假设:
如果源数组的项少于 5 个,您将需要一个包含 2 *(数组长度)项的数组,例如 [1, 2, 3] 变为 [1, 2, 3, 1, 2, 3]
如果源数组有 5 到 9 个项目,您将需要一个正好包含 10 个项目的数组,例如 [1, 2, 3, 4, 5, 6] 变为 [1, 2, 3, 4 , 5, 2, 3, 4, 5, 6]
一个演示程序是
public static void Main()
Console.WriteLine(String.Join(", ", headandtail(new int[]1, 2, 3)));
Console.WriteLine(String.Join(", ", headandtail(new int[]1, 2, 3, 4, 5, 6)));
Console.WriteLine(String.Join(", ", headandtail(new int[]1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)));
private static T[] headandtail<T>(T[] src)
int runlen = Math.Min(src.Length, 5);
T[] result = new T[2 * runlen];
Array.Copy(src, 0, result, 0, runlen);
Array.Copy(src, src.Length - runlen, result, result.Length - runlen, runlen);
return result;
在 O(1) 中运行;
如果您正在和同事一起玩代码拼图,那么所有的乐趣都在拼图中,不是吗?
这很简单。
src.Take(5).Concat(src.Reverse().Take(5).Reverse()).ToArray();
这在 O(n) 中运行。
【讨论】:
谢谢。太棒了。但是,主要标准是一行查询代码,而不是执行速度。但是,我真的很感谢您的出色算法!:) 从我这里拿一分!【参考方案4】:试试这个:
var result = someArray.Where((a, i) => i < 5 || i >= someArray.Length - 5);
【讨论】:
这是 Fabjan 答案的副本,也不太广泛。但是使用>=
使 5 对应于所取元素的实际数量是很聪明的,我也会用_
替换a
以指示未使用的变量。【参考方案5】:
这应该可以工作
someArray.Take(5).Concat(someArray.Skip(someArray.Count() - 5)).Take(5);
【讨论】:
这和 OP 写的有什么不同? 这是相当低效的。对Count()
的调用执行源的完整枚举,Skip
做了很多工作。如果函数要在IEnumerable<T>
上工作,最好只枚举一次源。
@Drew 在这种情况下,源是一个固定大小的数组,所以我假设Count()
使用重载来简单地返回数组的大小。【参考方案6】:
试试这个:
int[] someArray = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 ;
var firstFiveResults = someArray.Take(5);
var lastFiveResults = someArray.Reverse().Take(5).Reverse();
var result = firstFiveResults;
result = result.Concat(lastFiveResults);
第二个 Reverse() 将数字重新排序,因此您不会得到 18,17,16,15,14
【讨论】:
两个Reverse()
将比Skip(...)
效率低。
@JeppeStigNielsen - 这完全取决于实现。
@NPSF3000 当然可以。但令我惊讶的是,someArray.Reverse().Take(5).Reverse().ToArray()
对于大型数组实际上比someArray.Skip(someArray.Length - 5).ToArray()
快。我检查了源代码,结果发现当传递的IEnumerable<T>
具有附加结构时,Skip
没有进行任何优化,因此它以缓慢的方式迭代。而Reverse
创建一个内部结构Buffer<T>
,其实例构造函数在确定源实现ICollection<T>
时使用优化CopyTo(T[], int)
。所以我在实际实现方面错了!
@JeppeStigNielsen 呵呵,太搞笑了。当然,您只是在使用一种可能的 LINQ 实现,而给 Skip 带来性能提升是微不足道的。【参考方案7】:
请试试这个:
var result = someArray.Take(5).Union(someArray.Skip(someArray.Count() - 5).Take(5));
【讨论】:
Union
将丢弃重复项。该问题并不表示需要这种行为。 编辑:那么你对int[] someArray = 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ;
有什么期望?
@JeppeStigNielsen 你是对的。如果数组有重复记录,则无法正常工作。以上是关于使用 LINQ 通过一个查询从数组中获取前五个元素和后五个元素的主要内容,如果未能解决你的问题,请参考以下文章