39

I realize there are related questions, but all the answers seem to be work-arounds that avoid the heart of the matter. Does powershell have an operation that can use a scriptblock to aggregate elements of an array into a single value? This is what is known in other languages as aggregate or reduce or fold.

I can write it myself pretty easily, but given that its the base operation of any list processing, I would assume there's something built in I just don't know about.

So what I'm looking for is something like this

1..10 | Aggregate-Array {param($memo, $x); $memo * $x}
6
  • @Andie2302 ... no. I'm not sure how + fits my criteria? An aggregate function is - like I said - key to all list processing, it can actually be used very easily to construct all the other functions such as map, filter, groupby, foreach, etc Commented Aug 6, 2014 at 15:15
  • 1
    I would love to have the time to do a nice pretty answer, but I don't. See if this article heads you down a useful path: powershellmagazine.com/2013/12/23/… Commented Aug 6, 2014 at 16:07
  • 1
    Why not use Measure-Object? stackoverflow.com/a/19170783/58553 Commented Nov 10, 2016 at 12:42
  • @Peter because that only provides a few of the different use cases that reduce is used for, whereas reduce itself can be used for just about any array manipulation Commented Nov 10, 2016 at 18:28
  • 1
    Thats a good argument @Peter maybe add it as an answer? Commented Nov 11, 2016 at 23:04

5 Answers 5

63

There is not anything so obviously named as Reduce-Object but you can achieve your goal with Foreach-Object:

1..10 | Foreach {$total=1} {$total *= $_} {$total}

BTW there also isn't a Join-Object to merge two sequences of data based on some matching property.

Sign up to request clarification or add additional context in comments.

1 Comment

This is very clever! It fixed up some code I'm writing very nicely.
13

If you need Maximum, Minimum, Sum or Average you can use Measure-Object sadly it dosen´t handle any other aggregate method.

Get-ChildItem | Measure-Object -Property Length -Minimum -Maximum -Average

Comments

7

This is something I wanted to start for a while. Seeing this question, just wrote a pslinq (https://github.com/manojlds/pslinq) utility. The first and only cmdlet as of now is Aggregate-List, which can be used like below:

1..10 | Aggregate-List { $acc * $input } -seed 1
#3628800

Sum:

1..10 | Aggregate-List { $acc + $input }
#55

String reverse:

"abcdefg" -split '' | Aggregate-List { $input + $acc }
#gfedcba

PS: This is more an experiment

2 Comments

neato. Does it really have to be c# though? I'm pretty sure you can inject variables into a scope from PS (whatever psake does for example). I like c#, but seems a shame to mix paradigms for something like this.
@GeorgeMauer - True. I wanted to do this in C# however, nothing else :)
6

Ran into a similar issue recently. Here's a pure Powershell solution. Doesn't handle arrays within arrays and strings like the Javascript version does but maybe a good starting point.

function Reduce-Object {
    [CmdletBinding()]
    [Alias("reduce")]
    [OutputType([Int])]
    param(
        # Meant to be passed in through pipeline.
        [Parameter(Mandatory=$True,
                    ValueFromPipeline=$True,
                    ValueFromPipelineByPropertyName=$True)]
        [Array] $InputObject,

        # Position=0 because we assume pipeline usage by default.
        [Parameter(Mandatory=$True,
                    Position=0)]
        [ScriptBlock] $ScriptBlock,

        [Parameter(Mandatory=$False,
                    Position=1)]
        [Int] $InitialValue
    ) 

    begin {
        if ($InitialValue) { $Accumulator = $InitialValue }
    }

    process {
        foreach($Value in $InputObject) {
            if ($Accumulator) {
                # Execute script block given as param with values.
                $Accumulator = $ScriptBlock.InvokeReturnAsIs($Accumulator,  $Value)
            } else {
                # Contigency for no initial value given.
                $Accumulator = $Value
            }
        }
    }

    end {
        return $Accumulator
    }
}

1..10 | reduce {param($a, $b) $a + $b}
# Or
reduce -inputobject @(1,2,3,4) {param($a, $b) $a + $b} -InitialValue 2

Comments

1

There's a functional module I came upon https://github.com/chriskuech/functional that has a reduce-object, also merge-object, test-equality, etc. It's surprising that powershell has map (foreach-object) and filter (where-object) but not reduce. https://medium.com/swlh/functional-programming-in-powershell-876edde1aadb Even javascript has reduce for arrays. A reducer actually is very powerful. You can define map and filter in terms of it.

1..10 | reduce-object { $a * $b }

3628800

Measure-object can't sum [timespan]'s:

1..10 | % { [timespan]"$_" } | Reduce-Object { $a + $b } | ft

Days Hours Minutes Seconds Milliseconds
---- ----- ------- ------- ------------
55   0     0       0       0

Merge two objects:

@{a=1},@{b=2} | % { [pscustomobject]$_ } | Merge-Object -Strategy fail

b a
- -
2 1

1 Comment

Just eyeballing it, Chris' Powershell implementation of Reduce looks better than my own. The scriptblock validation and use of closures is interesting.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.