Powershell (测量对象-求和)的备选方案。求和

Powershell (测量对象-求和)的备选方案。求和,powershell,csv,sum,measure-object,Powershell,Csv,Sum,Measure Object,我陷入了以下情况: 我必须从CSV文件中获取信息。我使用导入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的列为关注值(“点数”) 这是我的第一个问题-->使用导入Csv,powershell会将此信息保存在[string]属性中。为了避

我陷入了以下情况: 我必须从CSV文件中获取信息。我使用
导入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
的列为关注值(“点数”)

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

| Select @{Name="Points";Expression={[decimal]$_.Points}}
现在我得到了一个
选中的.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]
,因为显示的数字是完全错误的。我想坚持使用十进制或整数,所以我有一个类似于
71123.4
之类的输出

有没有其他方法,这样我就不必使用
(measureobject-sum).sum


提前谢谢

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

Import-Csv .\data.csv |Group-Object Sender |ForEach-Object {
    [pscustomobject]@{
        Sender = $_.Name
        SumOfPoints = ($_.Group |Measure-Object Points -Sum).Sum
    }
}
测量对象
将自动将
字符串转换为
[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
    }
}
如前所述,通过使用分组,您可以在不损失小数精度的情况下获得总和:

# 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
    }
}

tl;博士

如果需要控制用于对数字求和的特定数字数据类型

  • 避免使用总是使用
    [double]
    计算的
    测量对象

  • 相反,使用LINQ(可在PSv3+中访问)并将转换为所需的数字类型


有用的答案向您展示了一种优雅的方法,可以将共享相同电子邮件地址的行分组的
列相加,并通过将点真正相加为
[decimal]
值进行改进

关于
-Sum
和浮点数据类型的一些一般要点:

您正确地陈述了:

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

确实:
测量对象-总和

  • 总是使用
    [double]
    值来汇总输入
  • 如果可能的话,它强制输入到
    [double]
    s,即使它们不是数字。
    • 如果无法将输入强制为
      [double]
      (例如,
      'foo'
      ),则会发出非终止错误,但对任何剩余输入继续求和
上述情况意味着,偶数字符串是可接受的
度量对象-Sum
输入,因为它们在求和过程中会根据需要转换为
[double]
。 这意味着您可以直接使用
导入Csv
命令,如以下示例所示(使用两个
[pscustomobject]
实例模拟
导入Csv
的输出):

71301.6000000006
[…]似乎我产生了“double”溢出

溢出将意味着超过a
[double]
中可存储的最大值,这是(a)不太可能的(
[double]::MaxValue
1.79769313486232E+308
,即大于308的10次方)和(b)将产生不同的症状;e、 g:

PS> ([double]::MaxValue, [double]::MaxValue | Measure-Object -Sum).Sum
∞  # represents positive infinity
然而,由于
[double]
类型的内部二进制表示法,您得到的是舍入错误,它并不总是有精确的十进制表示法,这可能导致令人困惑的计算结果;e、 g:

PS> 1.3 - 1.1 -eq 0.2
False # !! With [double]s, 1.3 - 1.1 is NOT exactly equal to 0.2
有关详细信息,请参阅

使用
[decimal]
值确实可以解决此问题
,但请注意,这是以较小的范围为代价的(实际上,精度为28位小数-最大数字的绝对值取决于小数点的位置;作为整数,它是
79228162514264337593543950335
,即接近8*1028)

如果您确实需要
[decimal]
s的精度,则必须避免
测量对象
,并自行求和

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

[Linq.Enumerable]::Sum(
  [decimal[]] @(
    $Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender}
  ).Points
)
  • 在管道命令周围使用
    @(…)
    (数组子表达式操作符),而不仅仅是
    (…)
    ,可以确保在管道恰好不返回任何行的情况下,整个命令不会失败。
    @(…)
    将非输出转换为空数组,对于该数组,
    .Sum()
    正确返回
    0

    • 如果没有它,
      [decimal[]]
      强制转换将导致
      $null
      ,PowerShell将无法找到
      .Sum()
      方法的
      [decimal[]
      类型的重载,并报告一个错误,“Sum”和参数计数发现多个不明确的重载:1”
  • 上面的命令总是要求将所有匹配的CSV行(表示为自定义对象)作为一个整体放入内存,而
    度量对象
    (与PowerShell管道中的大多数cmdlet一样)将逐个处理它们,这只需要c
    PS> ([double]::MaxValue, [double]::MaxValue | Measure-Object -Sum).Sum
    ∞  # represents positive infinity
    
    PS> 1.3 - 1.1 -eq 0.2
    False # !! With [double]s, 1.3 - 1.1 is NOT exactly equal to 0.2
    
    [Linq.Enumerable]::Sum(
      [decimal[]] @(
        $Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender}
      ).Points
    )
    
    # Replace $Imported_Csv with the original Import-Csv call to 
    # get memory-friendly one-by-one processing.
    $Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender} |
      foreach -Begin { [decimal] $sum = 0 } -Process { $sum += $_.Points } -End { $sum }