(Measure-Object -sum).Sum

时间:2019-03-17 09:39:46

标签: powershell csv sum measure-object

我处于以下情况: 我必须从CSV文件中获取信息。我使用Import-Csv导入了CSV。

我的原始数据如下:

45227;01.10.2018 03:24:00;Xxxx Xxxx Xxxxx x XX xxxxxxxxxxxxxx Xxxxx xxx Xxxxxxxxxxxxxxxxxxx;;3;XXXX;XXXX;XXX@XX.com;;;3.7;;

其中包含3.7的列是关注值(“点”)。

这是我的第一个问题->使用Import-Csv,powershell会将这些信息保存在[string]属性中。为了避免这种情况,我使用了以下行:

| Select @{Name="Points";Expression={[decimal]$_.Points}}

现在,我得到一个Selected.System.Management.Automation.PSCustomObject型对象,其中包含该属性作为[decimal]。现在,我想总结同一电子邮件地址使用的所有要点:

$Data[$Index].Points += (
  $Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender} | 
    measure Points -sum
).Sum

这似乎很好用,但是如果我打开$Data[$Index] | gm,我会得到:Points NoteProperty double Points=71301.6000000006

该属性已更改为[double]。我挖了一点,发现Powershell的GenericMeasureInfo.Sum属性只能返回一个Nullable<Double>实例作为属性值。

似乎我正在产生[double]的溢出,因为显示的数字是完全错误的。我想保留十进制或整数,所以我有类似71123.4之类的输出。

是否还有其他方法可以使用,所以我不必使用(Measure-Object -sum).Sum吗?

谢谢!

3 个答案:

答案 0 :(得分:3)

使用像Mathias这样的分组方法,就像我之前评论的那样,这是在不损失小数精度的情况下如何获得总和的方法:

# faking the Import-Csv here with a here-string.
# in real life, you would use: Import-Csv <yourdata.csv> -Delimiter ';'
$data = @"
Sender;Date;Description;Something;Number;Whatever;DontKnow;Email;Nothing;Zilch;Points;Empty;Nada
45227;01.10.2018 03:24:00;Xxxx Xxxx Xxxxx x XX xxxxxxxxxxxxxx Xxxxx xxx Xxxxxxxxxxxxxxxxxxx;;3;XXXV;XXXA;XXX@XX.com;;;3.7;;
45227;01.10.2018 03:24:00;Xxxx Xxxx Xxxxx x XX xxxxxxxxxxxxxx Xxxxx xxx Xxxxxxxxxxxxxxxxxxx;;3;XXXW;XXXB;XXX@XX.com;;;4.7;;
45226;01.10.2018 03:24:00;Xxxx Xxxx Xxxxx x XX xxxxxxxxxxxxxx Xxxxx xxx Xxxxxxxxxxxxxxxxxxx;;3;XXXX;XXXC;XXX@XX.com;;;4.777779;;
45225;01.10.2018 03:24:00;Xxxx Xxxx Xxxxx x XX xxxxxxxxxxxxxx Xxxxx xxx Xxxxxxxxxxxxxxxxxxx;;3;XXXY;XXXD;XXX@XX.com;;;4.8;;
45225;01.10.2018 03:24:00;Xxxx Xxxx Xxxxx x XX xxxxxxxxxxxxxx Xxxxx xxx Xxxxxxxxxxxxxxxxxxx;;3;XXXZ;XXXE;XXX@XX.com;;;4.9;;
"@ | ConvertFrom-Csv -Delimiter ';'

#get the two columns you need from the Csv and group them by Sender
$data | Select-Object Sender, Points | Group-Object Sender | ForEach-Object {
    # add the 'Points' values as decimal
    [decimal]$sum = 0
    foreach ($value in $_.Group.Points) { $sum += [decimal]$value }
    [PSCustomObject]@{
        Sender = $_.Name
        Sum    = $sum
    }
}

从上面的输出将是:

Sender      Sum
------      ---
45227       8,4
45226  4,777779
45225       9,7

答案 1 :(得分:3)

tl;博士

如果您需要控制用于 总结数字特定数字数据类型

  • 避免使用Measure-Object,而始终使用[double]计算。

  • 相反,请使用 LINQ Sum method (可在PSv3 +中访问),并将 cast 设置为所需的数字类型

[Linq.Enumerable]::Sum(
  [decimal[]] @(
    $Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender}
  ).Points
)

Mathias R. Jessen's有用的答案向您展示了一种优雅的方法,可以将Points列按共享相同电子邮件地址的行分组,而Theo's helpful answer则通过将这些点真正地求和为{{ 1}}值。

关于带有[decimal]和浮点数据类型的Measure-Object的一些一般要点

您正确声明:

  

属性[数据类型]更改为-Sum [...]我发现Powershell的double属性只能返回GenericMeasureInfo.Sum作为属性值。

确实:Nullable<Double>

  • 总是使用Measure-Object -Sum值来汇总输入。
  • 如果可能,将
  • 强制{em>输入到[double],即使它们不是数字。
    • 如果输入不能强制为[double](例如[double]),则会发出非终止错误,但所有剩余的输入将继续求和。

上面的内容暗示偶数字符串'foo' 的可接受输入,因为它们在求和期间将根据需要转换为Measure-Object -Sum。 这意味着您可以直接使用[double]命令 ,如以下示例所示(该示例使用两个Import-Csv实例来模拟[pscustomobject]的输出):< / p>

Import-Csv
  

PS> ([pscustomobject] @{ Points = '3.7' }, [pscustomobject] @{ Points = '1.2' } | Measure-Object Points -Sum).Sum 4.9 # .Points property values were summed correctly. [...]好像我正在产生“ double”溢出

溢出表示超出了71301.6000000006中可以存储的最大值,这是(a)不太可能([double][double]::MaxValue,即,大于10到308的幂)和(b)会产生不同的症状;例如:

1.79769313486232E+308
然而,由于PS> ([double]::MaxValue, [double]::MaxValue | Measure-Object -Sum).Sum ∞ # represents positive infinity 类型的原因,

您要做的是 舍入 错误。内部的 binary 表示,并不总是具有精确的 decimal 表示,这可能导致计算结果令人困惑;例如:

[double]

有关更多信息,请参见https://floating-point-gui.de/

使用PS> 1.3 - 1.1 -eq 0.2 False # !! With [double]s, 1.3 - 1.1 is NOT exactly equal to 0.2 值确实可以解决此问题,但是请注意,这是以较小范围为代价的(实际上,您可以获得28个十进制数字精度-最大数字的绝对值取决于小数点的位置;整数是[decimal],即接近8 * 10 28 )。

如果您确实需要79,228,162,514,264,337,593,543,950,335的精度,则必须避免[decimal]并进行自己的求和

在原始命令的上下文中,可以使用Measure-Object LINQ方法:

Sum
  • 在流水线命令周围使用[Linq.Enumerable]::Sum( [decimal[]] @( $Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender} ).Points ) (数组子表达式运算符)而不是仅使用@(...)确保在流水线返回没有行。 (...)将非输出转换为空数组,为此@(...)正确返回.Sum()

    • 没有它,0的转换将导致[decimal[]],PowerShell将无法找到$null方法的[decimal[]]类型的重载并报告错误,“为“和”找到多个模棱两可的重载,并且参数计数为1”。
  • 以上命令始终要求将所有匹配的CSV行(表示为自定义对象)全部作为一个整体存储到内存中 ,而.Sum()(作为PowerShell管道中的大多数cmdlet)将一个一个地处理它们,这只需要恒定的内存量(但速度较慢)。

如果不是一次将所有匹配的行一次加载到内存中,请使用Measure-ObjectForEach-Object)cmdlet,但是请注意,只有替换为实际的{{1} }调用已在内存中的数组foreach

Import-Csv

答案 2 :(得分:2)

我首先将所有发件人地址分组在一起,然后分别求和:

Import-Csv .\data.csv |Group-Object Sender |ForEach-Object {
    [pscustomobject]@{
        Sender = $_.Name
        SumOfPoints = ($_.Group |Measure-Object Points -Sum).Sum
    }
}

Measure-Object会自动将Points字符串强制转换为[double]-如果您需要更高的精度,可以像以前一样手动转换为[decimal]

Import-Csv .\data.csv |Select-Object Sender,@{Name="Points";Expression={[decimal]$_.Points}} |Group-Object Sender |ForEach-Object {
    [pscustomobject]@{
        Sender = $_.Name
        SumOfPoints = ($_.Group |Measure-Object Points -Sum).Sum
    }
}