如何以更可读方式实现列表滑动窗口并遍历获取前后元素?
更友好的滑动窗口(三元组)实现方案
嘿,这个问题我太懂了——之前用一堆三元判断写这种「获取当前元素前后邻居」的滑动窗口逻辑,过俩礼拜自己看都得反应半天!确实可以用LINQ的Prepend、Append、Zip这些方法来实现完全无三元判断、可读性拉满的版本。
核心思路:构建三个对齐的序列
我们需要三个序列来对应「前一个元素」「当前元素」「后一个元素」,然后把它们按位置一一配对:
- 前元素序列:在原序列开头补一个默认值(对应第一个元素没有前邻居的情况)
- 当前元素序列:就是原序列本身
- 后元素序列:把原序列跳过第一个元素,再在末尾补一个默认值(对应最后一个元素没有后邻居的情况)
然后用Zip方法把这三个序列按位置绑定,就能得到每个元素的(前, 当前, 后)三元组了。
具体实现代码
值类型版本(比如int、DateTime)
using System; using System.Collections.Generic; using System.Linq; public static class EnumerableExtensions { public static IEnumerable<(T? Prev, T Current, T? Next)> GetSlidingTriple<T>(this IEnumerable<T> source) where T : struct { if (source == null) throw new ArgumentNullException(nameof(source)); // 给原序列开头补一个默认值(第一个元素的前邻居) var withPrev = source.Prepend(default(T?)); // 原序列跳过第一个,末尾补默认值(最后一个元素的后邻居) var withNext = source.Skip(1).Append(default(T?)); // 先把前序列和当前序列配对,再和后序列配对 return withPrev.Zip(source, (prev, current) => (prev, current)) .Zip(withNext, (prevCurrent, next) => (prevCurrent.prev, prevCurrent.current, next)); } }
引用类型版本(比如string、自定义类)
如果是引用类型,不需要可空约束,直接用default(T)(也就是null)作为边界默认值:
public static IEnumerable<(T Prev, T Current, T Next)> GetSlidingTriple<T>(this IEnumerable<T> source) { if (source == null) throw new ArgumentNullException(nameof(source)); var withPrev = source.Prepend(default(T)); var withNext = source.Skip(1).Append(default(T)); return withPrev.Zip(source, (prev, current) => (prev, current)) .Zip(withNext, (prevCurrent, next) => (prevCurrent.prev, prevCurrent.current, next)); }
使用示例
var numbers = new List<int> { 1, 2, 3, 4, 5 }; foreach (var triple in numbers.GetSlidingTriple()) { Console.WriteLine($"Prev: {triple.Prev}, Current: {triple.Current}, Next: {triple.Next}"); }
输出结果:
Prev: , Current: 1, Next: 2 Prev: 1, Current: 2, Next: 3 Prev: 2, Current: 3, Next: 4 Prev: 3, Current: 4, Next: 5 Prev: 4, Current: 5, Next:
兼容旧.NET版本
如果你的项目用的是不支持Prepend和Append的旧版本(比如.NET Framework 4.7之前),可以自己实现这两个扩展方法:
public static IEnumerable<T> Prepend<T>(this IEnumerable<T> source, T item) { if (source == null) throw new ArgumentNullException(nameof(source)); yield return item; foreach (var element in source) yield return element; } public static IEnumerable<T> Append<T>(this IEnumerable<T> source, T item) { if (source == null) throw new ArgumentNullException(nameof(source)); foreach (var element in source) yield return element; yield return item; }
扩展:通用滑动窗口(任意大小)
如果以后需要更大的滑动窗口(比如窗口大小为5),可以用Select结合Skip和Take来实现,但对于「前后各一个元素」的固定三元场景,上面的方案是最简洁易读的。
内容的提问来源于stack exchange,提问作者johnny 5
相关产品推荐
相关产品推荐

