我正在编写一些 C# 代码来处理诸如移动平均线之类的问题,我经常需要使用 List / IEnumerable 并处理大量连续数据。F# Seq 模块有一个很棒的函数,windowed,它接受一个序列,返回一个连续元素块的序列。
C# 是否具有与 LINQ 相同的开箱即用功能?
我正在编写一些 C# 代码来处理诸如移动平均线之类的问题,我经常需要使用 List / IEnumerable 并处理大量连续数据。F# Seq 模块有一个很棒的函数,windowed,它接受一个序列,返回一个连续元素块的序列。
C# 是否具有与 LINQ 相同的开箱即用功能?
你总是可以SeqModule.Windowed
从 C# 调用,你只需要引用FSharp.Core.Dll
. 函数名称也有轻微损坏,因此您调用Windowed
而不是windowed
,以便它符合 C# 大写约定
您总是可以自己滚动(或从 F# 核心翻译一个):
let windowed windowSize (source: seq<_>) =
checkNonNull "source" source
if windowSize <= 0 then invalidArg "windowSize" (SR.GetString(SR.inputMustBeNonNegative))
seq { let arr = Microsoft.FSharp.Primitives.Basics.Array.zeroCreateUnchecked windowSize
let r = ref (windowSize-1)
let i = ref 0
use e = source.GetEnumerator()
while e.MoveNext() do
arr.[!i] <- e.Current
i := (!i + 1) % windowSize
if !r = 0 then
yield Array.init windowSize (fun j -> arr.[(!i+j) % windowSize])
else
r := (!r - 1) }
我的尝试看起来像这样,它比直接调用 F# 慢得多(正如 John Palmer 所建议的那样)。我猜这是因为 F# 使用了 Unchecked 数组。:
public static IEnumerable<T[]> Windowed<T>(this IEnumerable<T> list, int windowSize)
{
//Checks elided
var arr = new T[windowSize];
int r = windowSize - 1, i = 0;
using(var e = list.GetEnumerator())
{
while(e.MoveNext())
{
arr[i] = e.Current;
i = (i + 1) % windowSize;
if(r == 0)
yield return ArrayInit<T>(windowSize, j => arr[(i + j) % windowSize]);
else
r = r - 1;
}
}
}
public static T[] ArrayInit<T>(int size, Func<int, T> func)
{
var output = new T[size];
for(var i = 0; i < size; i++) output[i] = func(i);
return output;
}
John Palmer的回答很棒,这里有一个基于他的回答的例子。
var numbers = new[] {1, 2, 3, 4, 5};
var windowed = SeqModule.Windowed(2, numbers);
您可能(或不)想要将 ToArray() 添加到末尾,没有 ToArray,返回类型仍然在 F# 世界(序列)中。有了 ToArray,就回到了 C# 世界(数组)。
Reactive Extensions有一些操作符可以帮助解决这个问题,例如Buffer和Window。可以在实验分支中找到的交互式扩展将这些和大量附加运算符添加到 LINQ。