8

I am working on some C# code dealing with problems like moving averages, where I often need to take a List / IEnumerable and work on chunks of consecutive data. The F# Seq module has a great function, windowed, which taking in a Sequence, returns a sequence of chunks of consecutive elements.

Does C# have an equivalent function out-of-the-box with LINQ?

Stanislav Kralin
  • 11,070
  • 4
  • 35
  • 58
Mathias
  • 15,191
  • 9
  • 60
  • 92
  • The user who provided the accepted answer admitted it was wrong, you might want to consider choosing another now. – Kev Jan 17 '12 at 00:01

4 Answers4

9

You can always just call SeqModule.Windowed from C#, you just need to reference FSharp.Core.Dll. The function names are also slightly mangled, so you call Windowed rather than windowed, so that it fits with the C# capitalisation conventions

CoderDennis
  • 13,642
  • 9
  • 69
  • 105
John Palmer
  • 25,356
  • 3
  • 48
  • 67
2

John Palmer's answer is great, here is an example based on his answer.

var numbers = new[] {1, 2, 3, 4, 5}; 
var windowed = SeqModule.Windowed(2, numbers);

You may (or not) want to add ToArray() to the end, without ToArray, the return type is still in F# world (Sequence). With ToArray, it is back to C# world (Array).

enter image description here

Rm558
  • 4,621
  • 3
  • 38
  • 43
2

You could always roll your own (or translate the one from F# core):

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) }

My attempt looks like this, it's way slower than just calling F# directly (as suggested by John Palmer). I'm guessing it's because of F# using an Unchecked array.:

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;
}
Benjol
  • 63,995
  • 54
  • 186
  • 268
  • Replace the call to `ArrayInit` with `var arrR = new T[windowSize]; for (int j = 0; j < windowSize; j++) { arrR[j] = arr[(i + j) % windowSize]; }` and `r = r - 1` with `r--` and--in my tests anyway--it was slightly faster than `Seq.windowed`. – Daniel Jan 16 '12 at 15:37
  • 1
    `Seq.windowed` uses `zeroCreateUnchecked`, but it just skips validation of the `size` parameter (i.e., `if size < 0 then invalidArg ...`). It doesn't avoid bounds checking. That's done at the discretion of the JITer, I believe. – Daniel Jan 16 '12 at 15:44
  • @Daniel, nice to see someone took the bait :) I can't get your results though. If I do `var list = Enumerable.Range(0, 100000); var sw = Stopwatch.StartNew(); int count = list.Windowed(15).Count(); sw.Stop();` and then the same thing with `Microsoft.FSharp.Collections.SeqModule.Windowed` (on a new range), the C# always takes roughly twice as long... – Benjol Jan 17 '12 at 05:46
  • Are you running the C# in the debugger? Run both versions in release mode. – Daniel Jan 17 '12 at 15:08
  • @Daniel, should have thought of that! C# still slower than F# for me though, though not twice as long. http://pastebin.com/TVgfFahp – Benjol Jan 18 '12 at 06:15
  • The [C#](http://ideone.com/9MeYS) and [F#](http://ideone.com/xwhgt) versions perform almost identically on ideone. The C# is negligibly slower, but ideone uses Mono. My original tests were on Windows. – Daniel Jan 18 '12 at 15:27
1

The Reactive Extensions have a few operators to help with this, such as Buffer and Window. The Interactive Extensions, which can be found in the experimental branch, add these and a significant number of additional operators to LINQ.