My Technical Notes

Thursday, 3 March 2016

SkipUntil and TakeUntil in PowerShell

Given a list `[ x1, x2, x3, ..., xn]` and a predicate `p`, `SkipUntil` will skip over the elements until it finds an `xi` such that `p(xi)` holds. At this point it returns `xi` together with the rest of the list, therefore returning `[xi, x(i + 1), ..., xn]`. We can optionally exclude `xi`.

Given a list `[ x1, x2, x3, ..., xn]` and a predicate `p`, `TakeUntil` will yield each element until it finds an `xi` such that `p(xi)` holds. At this point it returns `xi` and stops iterating over the list, therefore returning `[ x1, ..., xi]`. We can optionally exclude `xi`.

Here is the PowerShell module:


Function SkipUntil-Object
{
    param(
        [Parameter(Mandatory=$true, Position=0)][ScriptBlock]$Predicate,
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]$ObjectSet,
        [Parameter()][alias("x")][switch]$ExcludeMatch
    )

    begin {
        $foundMatch = $false;
    }

    process {
        foreach ($o in $ObjectSet) {
            if ($foundMatch) {
                $o;
            } else {
                if ($o | % $Predicate) {
                    $foundMatch = $true;
                    if (-not $ExcludeMatch) {
                        $o;
                    }
                }
            }

        }
    }
}

Set-Alias skipuntil SkipUntil-Object


Function TakeUntil-Object
{
    param(
        [Parameter(Mandatory=$true, Position=0)][ScriptBlock]$Predicate,
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]$ObjectSet,
        [Parameter()][alias("x")][switch]$ExcludeMatch
    )

    begin {
        $foundMatch = $false;
    }

    process {
        foreach ($o in $ObjectSet) {
            if ($o | % $Predicate) {
                if (-not $ExcludeMatch) {
                    $o;
                }
                
                throw New-Object System.Management.Automation.PipelineStoppedException
            } else {
                $o;
            }
        }
    }
}

Set-Alias takeuntil TakeUntil-Object

Export-ModuleMember -Function * -Alias *

Code samples


1..100 | takeuntil { $_ -eq 50 }
# => 1..50
'matthew', 'mark', 'luke', 'john' | skipuntil { $_ -match '^l.*' }
# => 'luke', 'john'
'matthew', 'mark', 'luke', 'john' | skipuntil { $_ -match '^l.*' } -x
# => 'john'  because we have used the -ExcludeMatch switch

References

  • $linq: Javascript LINQ library This was the only page online I could find which included, by default the matched element `xi` in the result set.
  • ReactiveX: SkipUntil - although they are discarding the item at which the match occurs and I am including it (by default), it still serves as a nice illustration of the concept.
  • powershell.com: Cancelling a Pipeline - tells us how to cancel a pipeline, used in the `TakeUntil-Object` implementation.
  • jaredpar: PowerShell LINQ Take-Count and Take-While - this provided some inspiration for my implementation.
  • GitHub: MoreLINQ - This extension library contains an implementation of TakeUntil, which takes upto and including the matched element with no option for match exclusion, and an implementation of SkipUntil which skips all elements upto and including the matched element, i.e. it excludes the match, with no option of inclusion.

No comments: