在C#中是否有与F# Seq.windowed等价的功能?

8

我正在处理一些与移动平均值等问题有关的C#代码,经常需要对连续数据块进行操作,这时我需要使用列表/List或IEnumerable。F# Seq模块有一个很好的函数windowed,它接受一个序列并返回一系列连续元素的块。

C#是否内置具有类似功能的LINQ函数?


提供被采纳答案的用户承认其错误,您可能需要考虑选择另一个答案。 - Kev
4个回答

9

您可以随时从C#调用 SeqModule.Windowed,您只需要引用 FSharp.Core.Dll。函数名称也略有变化,因此您需要调用Windowed而不是windowed,以适应C#的大小写约定。


在这里,我一直这样做!Seq.singleton、FSharpSet等等。当我必须使用C#编码时,我经常使用F# stdlib。如果没有它,我该怎么活呢! - kkm
1
实际上是 SeqModule.Windowed - Mauricio Scheffer
1
我喜欢在这里找到一个已经被我点赞和编辑过的答案,但是我完全忘记了它。 - CoderDennis

2

您可以自己编写(或翻译来自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;
}

var arrR = new T[windowSize]; for (int j = 0; j < windowSize; j++) { arrR[j] = arr[(i + j) % windowSize]; } 替换对 ArrayInit 的调用,用 r-- 替换 r = r - 1。在我的测试中,它比 Seq.windowed 稍微快一点。 - Daniel
1
Seq.windowed 使用 zeroCreateUnchecked,但它只是跳过了对 size 参数的验证(即 if size < 0 then invalidArg ...)。它并没有避免边界检查。我相信这是由 JITer 自行决定的。 - Daniel
@Daniel,很高兴看到有人上钩 :) 但是我无法得到你的结果。如果我执行 var list = Enumerable.Range(0, 100000); var sw = Stopwatch.StartNew(); int count = list.Windowed(15).Count(); sw.Stop(); 然后在一个新的范围内执行相同的操作 Microsoft.FSharp.Collections.SeqModule.Windowed,C# 总是需要大约两倍的时间... - Benjol
你是否在调试器中运行C#?请在发布模式下同时运行两个版本。 - Daniel
@Daniel,应该想到这个!对我来说,C#仍然比F#慢,但不是两倍长。http://pastebin.com/TVgfFahp - Benjol
C#F#版本在ideone上的表现几乎相同。C#稍微慢一些,但ideone使用Mono。我的原始测试是在Windows上进行的。 - Daniel

2
约翰·帕尔默的回答很棒,这里有一个基于他的答案的例子。
var numbers = new[] {1, 2, 3, 4, 5}; 
var windowed = SeqModule.Windowed(2, numbers);

你可能(或者不想)想在末尾添加ToArray(),如果没有ToArray,返回类型仍然在 F# world 中(Sequence)。使用 ToArray,它就回到了 C# world 中(Array)。

enter image description here


1

响应式扩展有一些运算符可以帮助处理这个问题,例如缓冲区窗口。交互式扩展可以在实验分支中找到,它们添加了这些运算符以及大量其他的LINQ运算符。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接