diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs index 977fd640670..2b10d108e11 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs @@ -35,7 +35,7 @@ public sealed class GenericMeasureInfo : MeasureInfo /// public GenericMeasureInfo() { - Average = Sum = Maximum = Minimum = null; + Average = Sum = Maximum = Minimum = StdDeviation = null; } /// @@ -72,6 +72,13 @@ public GenericMeasureInfo() /// /// public double? Minimum { get; set; } + + /// + /// + /// The Standard Deviation of property values + /// + /// + public double? StdDeviation { get; set; } } /// @@ -91,7 +98,7 @@ public sealed class GenericObjectMeasureInfo : MeasureInfo /// public GenericObjectMeasureInfo() { - Average = Sum = null; + Average = Sum = StdDeviation = null; Maximum = Minimum = null; } @@ -129,6 +136,13 @@ public GenericObjectMeasureInfo() /// /// public object Minimum { get; set; } + + /// + /// + /// The Standard Deviation of property values + /// + /// + public double? StdDeviation { get; set; } } @@ -227,6 +241,8 @@ private class Statistics // Generic/Numeric statistics internal double sum = 0.0; + internal double stdDeviation = 0.0; + internal List stdDeviationNumbers = new List(); internal object max = null; internal object min = null; @@ -265,6 +281,27 @@ public MeasureObjectCommand() #endregion Common parameters in both sets + /// + /// Set to true if Standard Deviation is to be returned + /// + /// + [Parameter(ParameterSetName = GenericParameterSet)] + public SwitchParameter StdDeviation + { + get + { + return _measureStdDeviation; + } + set + { + _measureStdDeviation = value; + if(value == true) + _measureAverage = true; + } + } + + private bool _measureStdDeviation; + /// /// Set to true is Sum is to be returned /// @@ -713,6 +750,8 @@ private void AnalyzeNumber(double numValue, Statistics stat) { if (_measureSum || _measureAverage) stat.sum += numValue; + if (_measureStdDeviation) + stat.stdDeviationNumbers.Add(numValue); } /// @@ -793,6 +832,7 @@ private MeasureInfo CreateGenericMeasureInfo(Statistics stat, bool shouldUseGene { double? sum = null; double? average = null; + double? stdDeviation = null; object max = null; object min = null; @@ -800,8 +840,33 @@ private MeasureInfo CreateGenericMeasureInfo(Statistics stat, bool shouldUseGene { if (_measureSum) sum = stat.sum; + if (_measureAverage && stat.count > 0) average = stat.sum / stat.count; + + if(_measureStdDeviation && !_measureAverage) { + ErrorRecord errorRecord = new ErrorRecord( + PSTraceSource.NewArgumentException("Average"), + "AverageSwitchNotSet", + ErrorCategory.InvalidArgument, + null); + + errorRecord.ErrorDetails = new ErrorDetails(this, "MeasureObjectStrings", "AverageSwitchNotSet", "Average"); + WriteError(errorRecord); + } + + if (_measureStdDeviation && _measureAverage && stat.count > 0) + { + var sumOfDerivation = 0.0; + + foreach (double n in stat.stdDeviationNumbers) + { + var m = n - (double)average; + sumOfDerivation += m * m; + } + + stdDeviation = Math.Round(Math.Sqrt(sumOfDerivation / (stat.stdDeviationNumbers.Count - 1)), 4); + } } if (_measureMax) @@ -838,6 +903,7 @@ private MeasureInfo CreateGenericMeasureInfo(Statistics stat, bool shouldUseGene gmi.Count = stat.count; gmi.Sum = sum; gmi.Average = average; + gmi.StdDeviation = stdDeviation; if (null != max) { gmi.Maximum = (double)max; diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/MeasureObjectStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/MeasureObjectStrings.resx index 8211114c335..bccbd5db10c 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/MeasureObjectStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/MeasureObjectStrings.resx @@ -126,4 +126,7 @@ Input object "{0}" is not numeric. + + StdDeviation was requested and requires the average to be calculated, however '-Average' was set to $false. + diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Measure-Object.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Measure-Object.Tests.ps1 index e82baa82d7f..d708e0bdeaf 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Measure-Object.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Measure-Object.Tests.ps1 @@ -1,28 +1,49 @@ Describe "Measure-Object" -Tags "CI" { - $testObject = 1,3,4 + $testObject = 1, 3, 4 It "Should be able to be called without error" { - { Measure-Object | Out-Null } | Should Not Throw + { Measure-Object | Out-Null } | Should Not Throw } It "Should be able to call on piped input" { - { $testObject | Measure-Object } | Should Not Throw + { $testObject | Measure-Object } | Should Not Throw } It "Should be able to count the number of objects input to it" { - $($testObject | Measure-Object).Count | Should Be $testObject.Length + $($testObject | Measure-Object).Count | Should Be $testObject.Length + } + + It "Should calculate Standard Deviation" { + $actual = ($testObject | Measure-Object -Average -StdDeviation) + $actual.StdDeviation | Should Be 1.5275 + } + + It "Should throw if Standard Deviation with -Average not specified" { + #{$testObject | Measure-Object -StdDeviation} | Should Throw + $actual = $testObject | Measure-Object -StdDeviation + $actual.StdDeviation | Should Be 1.5275 + } + + It 'Should throw if Standard Deviation requested and -Average:$false' { + try { + $Error.Clear() + $testObject | Measure-Object -StdDeviation -Average:$false -ErrorAction Stop + } + catch { + $Error.Count | should be 1 + } } It "Should be able to count using the Property switch" { - $expected = $(Get-ChildItem $TestDrive).Length - $actual = $(Get-ChildItem $TestDrive | Measure-Object -Property Length).Count + $expected = $(Get-ChildItem $TestDrive).Length + $actual = $(Get-ChildItem $TestDrive | Measure-Object -Property Length).Count - $actual | Should Be $expected + $actual | Should Be $expected } It "Should be able to use wildcards for the Property argument" { $data = [pscustomobject]@{ A1 = 1; A2 = 2; C3 = 3 }, - [pscustomobject]@{ A1 = 1; A2 = 2; A3 = 3 } + [pscustomobject]@{ A1 = 1; A2 = 2; A3 = 3 } $actual = $data | Measure-Object -Property A* -Sum $actual.Count | Should Be 3 $actual[0].Property | Should Be A1 @@ -37,133 +58,122 @@ Describe "Measure-Object" -Tags "CI" { } Context "Numeric tests" { - It "Should be able to sum" { - $actual = $testObject | Measure-Object -Sum - $expected = 0 + It "Should be able to sum" { + $actual = $testObject | Measure-Object -Sum + $expected = 0 - foreach ( $obj in $testObject ) - { - $expected += $obj - } + foreach ( $obj in $testObject ) { + $expected += $obj + } - $actual.Sum | Should Be $expected - } + $actual.Sum | Should Be $expected + } - It "Should be able to average" { - $actual = $testObject | Measure-Object -Average - $expected = 0 + It "Should be able to average" { + $actual = $testObject | Measure-Object -Average + $expected = 0 - foreach ( $obj in $testObject ) - { - $expected += $obj - } + foreach ( $obj in $testObject ) { + $expected += $obj + } - $expected /= $testObject.length + $expected /= $testObject.length - $actual.Average | Should Be $expected - } + $actual.Average | Should Be $expected + } - It "Should be able to return a minimum" { - $actual = $testObject | Measure-Object -Minimum - $expected = $testObject[0] + It "Should be able to return a minimum" { + $actual = $testObject | Measure-Object -Minimum + $expected = $testObject[0] - for ($i=0; $i -lt $testObject.length; $i++) - { - if ( $testObject[$i] -lt $expected ) - { + for ($i = 0; $i -lt $testObject.length; $i++) { + if ( $testObject[$i] -lt $expected ) { - $expected = $testObject[$i] - } - } + $expected = $testObject[$i] + } + } - $actual.Minimum | Should Be $expected - } + $actual.Minimum | Should Be $expected + } - It "Should be able to return a minimum when multiple objects are the minimum" { - $testMinimum = 1,1,2,4 - $actual = $testMinimum | Measure-Object -Minimum - $expected = $testMinimum[0] + It "Should be able to return a minimum when multiple objects are the minimum" { + $testMinimum = 1, 1, 2, 4 + $actual = $testMinimum | Measure-Object -Minimum + $expected = $testMinimum[0] - for ($i=1; $i -lt $testMinimum.length; $i++) - { - if ( $testMinimum[$i] -lt $expected ) - { + for ($i = 1; $i -lt $testMinimum.length; $i++) { + if ( $testMinimum[$i] -lt $expected ) { - $expected = $testMinimum[$i] - } - } + $expected = $testMinimum[$i] + } + } - $actual.Minimum | Should Be $expected - } + $actual.Minimum | Should Be $expected + } - It "Should be able to return a maximum" { - $actual = $testObject | Measure-Object -Maximum - $expected = $testObject[0] + It "Should be able to return a maximum" { + $actual = $testObject | Measure-Object -Maximum + $expected = $testObject[0] - for ($i=1; $i -lt $testObject.length; $i++) - { - if ( $testObject[$i] -gt $expected ) - { + for ($i = 1; $i -lt $testObject.length; $i++) { + if ( $testObject[$i] -gt $expected ) { - $expected = $testObject[$i] - } - } + $expected = $testObject[$i] + } + } - $actual.Maximum | Should Be $expected - } + $actual.Maximum | Should Be $expected + } - It "Should be able to return a maximum when multiple objects are the maximum" { - $testMaximum = 1,3,5,5 - $actual = $testMaximum | Measure-Object -Maximum - $expected = $testMaximum[0] + It "Should be able to return a maximum when multiple objects are the maximum" { + $testMaximum = 1, 3, 5, 5 + $actual = $testMaximum | Measure-Object -Maximum + $expected = $testMaximum[0] - for ($i=1; $i -lt $testMaximum.length; $i++) - { - if ( $testMaximum[$i] -gt $expected ) - { + for ($i = 1; $i -lt $testMaximum.length; $i++) { + if ( $testMaximum[$i] -gt $expected ) { - $expected = $testMaximum[$i] - } - } + $expected = $testMaximum[$i] + } + } - $actual.Maximum | Should Be $expected - } + $actual.Maximum | Should Be $expected + } } Context "String tests" { - $nl = [Environment]::NewLine + $nl = [Environment]::NewLine - $testString = "HAD I the heavens' embroidered cloths,$nl Enwrought with golden and silver light,$nl The blue and the dim and the dark cloths$nl Of night and light and the half light,$nl I would spread the cloths under your feet:$nl But I, being poor, have only my dreams;$nl I have spread my dreams under your feet;$nl Tread softly because you tread on my dreams." + $testString = "HAD I the heavens' embroidered cloths,$nl Enwrought with golden and silver light,$nl The blue and the dim and the dark cloths$nl Of night and light and the half light,$nl I would spread the cloths under your feet:$nl But I, being poor, have only my dreams;$nl I have spread my dreams under your feet;$nl Tread softly because you tread on my dreams." - It "Should be able to count the number of words in a string" { - $expectedLength = $testString.Replace($nl,"").Split().length - $actualLength = $testString | Measure-Object -Word + It "Should be able to count the number of words in a string" { + $expectedLength = $testString.Replace($nl, "").Split().length + $actualLength = $testString | Measure-Object -Word - $actualLength.Words | Should Be $expectedLength - } + $actualLength.Words | Should Be $expectedLength + } - It "Should be able to count the number of characters in a string" { - $expectedLength = $testString.length - $actualLength = $testString | Measure-Object -Character + It "Should be able to count the number of characters in a string" { + $expectedLength = $testString.length + $actualLength = $testString | Measure-Object -Character - $actualLength.Characters | Should Be $expectedLength - } + $actualLength.Characters | Should Be $expectedLength + } - It "Should be able to count the number of lines in a string" { - $expectedLength = $testString.Split($nl, [System.StringSplitOptions]::RemoveEmptyEntries).length - $actualLength = $testString | Measure-Object -Line + It "Should be able to count the number of lines in a string" { + $expectedLength = $testString.Split($nl, [System.StringSplitOptions]::RemoveEmptyEntries).length + $actualLength = $testString | Measure-Object -Line - $actualLength.Lines | Should Be $expectedLength - } + $actualLength.Lines | Should Be $expectedLength + } } } Describe "Measure-Object DRT basic functionality" -Tags "CI" { - BeforeAll { - if(-not ([System.Management.Automation.PSTypeName]'TestMeasureGeneric').Type) - { - Add-Type -TypeDefinition @" + BeforeAll { + if (-not ([System.Management.Automation.PSTypeName]'TestMeasureGeneric').Type) { + Add-Type -TypeDefinition @" [System.Flags] public enum TestMeasureGeneric : uint { @@ -173,10 +183,9 @@ Describe "Measure-Object DRT basic functionality" -Tags "CI" { TestMin = 8 } "@ - } - if(-not ([System.Management.Automation.PSTypeName]'TestMeasureText').Type) - { - Add-Type -TypeDefinition @" + } + if (-not ([System.Management.Automation.PSTypeName]'TestMeasureText').Type) { + Add-Type -TypeDefinition @" [System.Flags] public enum TestMeasureText : uint { @@ -186,123 +195,105 @@ Describe "Measure-Object DRT basic functionality" -Tags "CI" { TestLine = 8 } "@ - } - $employees = [pscustomobject]@{"FirstName"="joseph"; "LastName"="smith"; "YearsInMS"=15}, - [pscustomobject]@{"FirstName"="paul"; "LastName"="smith"; "YearsInMS"=15}, - [pscustomobject]@{"FirstName"="mary jo"; "LastName"="soe"; "YearsInMS"=5}, - [pscustomobject]@{"FirstName"="edmund`todd `n"; "LastName"="bush"; "YearsInMS"=9} - } - - It "Measure-Object with Generic enum value options combination should work"{ + } + $employees = [pscustomobject]@{"FirstName" = "joseph"; "LastName" = "smith"; "YearsInMS" = 15}, + [pscustomobject]@{"FirstName" = "paul"; "LastName" = "smith"; "YearsInMS" = 15}, + [pscustomobject]@{"FirstName" = "mary jo"; "LastName" = "soe"; "YearsInMS" = 5}, + [pscustomobject]@{"FirstName" = "edmund`todd `n"; "LastName" = "bush"; "YearsInMS" = 9} + } + + It "Measure-Object with Generic enum value options combination should work" { $flags = [TestMeasureGeneric]0 - $property = "FirstName" - $testSum = ($flags -band [TestMeasureGeneric]::TestSum) -gt 0 + $property = "FirstName" + $testSum = ($flags -band [TestMeasureGeneric]::TestSum) -gt 0 $testAverage = ($flags -band [TestMeasureGeneric]::TestAverage) -gt 0 $testMax = ($flags -band [TestMeasureGeneric]::TestMax) -gt 0 $testMin = ($flags -band [TestMeasureGeneric]::TestMin) -gt 0 - $result = $employees | Measure-Object -Sum:$testSum -Average:$testAverage -Max:$testMax -Min:$testMin -Prop $property - $result.Count | Should Be 4 - $result.Sum | Should BeNullOrEmpty - $result.Average | Should BeNullOrEmpty - $result.Max | Should BeNullOrEmpty - $result.Min | Should BeNullOrEmpty - for ($i = 1; $i -lt 8 * 2; $i++) - { - $flags = [TestMeasureGeneric]$i - $property = "YearsInMS" - $testSum = ($flags -band [TestMeasureGeneric]::TestSum) -gt 0 - $testAverage = ($flags -band [TestMeasureGeneric]::TestAverage) -gt 0 - $testMax = ($flags -band [TestMeasureGeneric]::TestMax) -gt 0 - $testMin = ($flags -band [TestMeasureGeneric]::TestMin) -gt 0 - $result = $employees | Measure-Object -Sum:$testSum -Average:$testAverage -Max:$testMax -Min:$testMin -Prop $property - $result.Count | Should Be 4 - if($testSum) - { - $result.Sum | Should Be 44 - } - else - { - $result.Sum | Should BeNullOrEmpty - } - - if($testAverage) - { - $result.Average | Should Be 11 - } - else - { - $result.Average | Should BeNullOrEmpty - } - - if($testMax) - { - $result.Maximum | Should Be 15 - } - else - { - $result.Maximum | Should BeNullOrEmpty - } - - if($testMin) - { - $result.Minimum | Should Be 5 - } - else - { - $result.Minimum | Should BeNullOrEmpty - } - } + $result = $employees | Measure-Object -Sum:$testSum -Average:$testAverage -Max:$testMax -Min:$testMin -Prop $property + $result.Count | Should Be 4 + $result.Sum | Should BeNullOrEmpty + $result.Average | Should BeNullOrEmpty + $result.Max | Should BeNullOrEmpty + $result.Min | Should BeNullOrEmpty + for ($i = 1; $i -lt 8 * 2; $i++) { + $flags = [TestMeasureGeneric]$i + $property = "YearsInMS" + $testSum = ($flags -band [TestMeasureGeneric]::TestSum) -gt 0 + $testAverage = ($flags -band [TestMeasureGeneric]::TestAverage) -gt 0 + $testMax = ($flags -band [TestMeasureGeneric]::TestMax) -gt 0 + $testMin = ($flags -band [TestMeasureGeneric]::TestMin) -gt 0 + $result = $employees | Measure-Object -Sum:$testSum -Average:$testAverage -Max:$testMax -Min:$testMin -Prop $property + $result.Count | Should Be 4 + if ($testSum) { + $result.Sum | Should Be 44 + } + else { + $result.Sum | Should BeNullOrEmpty + } + + if ($testAverage) { + $result.Average | Should Be 11 + } + else { + $result.Average | Should BeNullOrEmpty + } + + if ($testMax) { + $result.Maximum | Should Be 15 + } + else { + $result.Maximum | Should BeNullOrEmpty + } + + if ($testMin) { + $result.Minimum | Should Be 5 + } + else { + $result.Minimum | Should BeNullOrEmpty + } + } } - It "Measure-Object with Text combination should work"{ - for ($i = 1; $i -lt 8 * 2; $i++) - { - $flags = [TestMeasureText]$i - $property = "FirstName" - $testIgnoreWS = ($flags -band [TestMeasureText]::TestIgnoreWS) -gt 0 - $testCharacter = ($flags -band [TestMeasureText]::TestCharacter) -gt 0 - $testWord = ($flags -band [TestMeasureText]::TestWord) -gt 0 - $testLine = ($flags -band [TestMeasureText]::TestLine) -gt 0 - $result = $employees | Measure-Object -IgnoreWhiteSpace:$testIgnoreWS -Character:$testCharacter -Word:$testWord -Line:$testLine -Prop $property - - if($testCharacter) - { - if($testIgnoreWS) - { - $result.Characters | Should Be 25 - } - else - { - $result.Characters | Should Be 29 - } - } - else - { - $result.Characters | Should BeNullOrEmpty - } - - if($testWord) - { - $result.Words | Should Be 6 - } - else - { - $result.Words | Should BeNullOrEmpty - } - - if($testLine) - { - $result.Lines | Should Be 4 - } - else - { - $result.Lines | Should BeNullOrEmpty - } - } + It "Measure-Object with Text combination should work" { + for ($i = 1; $i -lt 8 * 2; $i++) { + $flags = [TestMeasureText]$i + $property = "FirstName" + $testIgnoreWS = ($flags -band [TestMeasureText]::TestIgnoreWS) -gt 0 + $testCharacter = ($flags -band [TestMeasureText]::TestCharacter) -gt 0 + $testWord = ($flags -band [TestMeasureText]::TestWord) -gt 0 + $testLine = ($flags -band [TestMeasureText]::TestLine) -gt 0 + $result = $employees | Measure-Object -IgnoreWhiteSpace:$testIgnoreWS -Character:$testCharacter -Word:$testWord -Line:$testLine -Prop $property + + if ($testCharacter) { + if ($testIgnoreWS) { + $result.Characters | Should Be 25 + } + else { + $result.Characters | Should Be 29 + } + } + else { + $result.Characters | Should BeNullOrEmpty + } + + if ($testWord) { + $result.Words | Should Be 6 + } + else { + $result.Words | Should BeNullOrEmpty + } + + if ($testLine) { + $result.Lines | Should Be 4 + } + else { + $result.Lines | Should BeNullOrEmpty + } + } } - It "Measure-Object with multiple lines should work"{ - $result = "123`n4"|measure-object -line - $result.Lines | Should Be 2 - } + It "Measure-Object with multiple lines should work" { + $result = "123`n4"|measure-object -line + $result.Lines | Should Be 2 + } }