Anthony Mastrean
Anthony Mastrean

Reputation: 22404

Does PowerShell have a "window" function?

I was looking for a "window" function like F#'s Seq.windowed or the Reactive extensions Window. It looks like it would be provided by the likes of Select-Object (which already has take / skip functionality), but it is not.

If nothing is readily available, any ideas on implementing "window" without unnecessary procedural looping?

I'm looking for something that works with the PowerShell pipeline nicely.

Upvotes: 3

Views: 305

Answers (2)

user4003407
user4003407

Reputation: 22132

You need to use some kind of queue to accumulate and rotate enough previous pipeline items:

function Window {
    param($Size)
    begin {
        $Queue = [Collections.Queue]::new($Size)
    }
    process {
        $Queue.Enqueue($_)
        if($Queue.Count -eq $Size) {
            @(
                ,$Queue.ToArray()
                [void]$Queue.Dequeue()
            )
        }
    }
}

And you can use it like this:

1..10 | Window 4 | Window 3 | Format-Custom -Expand CoreOnly

Upvotes: 3

briantist
briantist

Reputation: 47842

There is no such built-in as far as I know. PowerShell's handling of the pipeline and in particular its strong insistence on unrolling enumerables makes it a bit difficult to be able to just write a function to pipe into, but as a general expression:

$a = 1..10  # an array contain 1 through 10
$w = 4      # window size of 4

0..($a.Length-$w) | ForEach-Object -Process {
    $a[$_..($_+$w-1)]
}

That does process arrays that are the window you want. It's easier to see like this:

0..($a.Length-$w) | ForEach-Object -Process {
    "This is my window: $($a[$_..($_+$w-1)])"
}

The thing is, if you tried to do the above this way:

0..($a.Length-$w) | ForEach-Object -Process {
    $a[$_..($_+$w-1)]
} | ForEach-Object -Process {
    "This is my window: $_"
}

You'd be very disappointed as the "window" array was unrolled before being piped into ForEach-Object; a problem you'd have with any pipeline function you wrote to handle this as well.

There's no way to compensate for it within the function, you'd have to take care to use it inside the ForEach-Object block because even on assignment:

$myWindows = 0..($a.Length-$w) | ForEach-Object -Process {
    $a[$_..($_+$w-1)]
}

It would be unrolled.

So then you would need something like:

0..($a.Length-$w) | ForEach-Object -Begin {
    $myWindows = @()
} -Process {
    $myWindows += @(,$a[$_..($_+$w-1)])
}

Which you could then use like:

$myWindows | ForEach-Object -Process {
    "This is my window: $_"
}  

Upvotes: 1

Related Questions