(Measure-Object -sum).Sum 的替代方法
Alternatives to (Measure-Object -sum).Sum
我遇到了以下情况:
我必须从 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
的列是感兴趣的值 ("Points")。
我的第一个问题来了 --> 使用 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
?
提前致谢!
我首先将所有发件人地址分组在一起,然后分别对它们求和:
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
}
}
像 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
tl;dr:
如果您需要控制特定数值数据类型用于求和 数字:
避免Measure-Object
,它总是使用[double]
计算。
而是使用 LINQ Sum
method(可在 PSv3+ 中访问)和 cast 到所需的数字类型:
[Linq.Enumerable]::Sum(
[decimal[]] @(
$Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender}
).Points
)
helpful answer shows you an elegant way to sum your Points
column grouped by rows that share the same email address and 通过真正将点相加为 [decimal]
值对其进行了改进。
一些 关于 Measure-Object
与 -Sum
和浮点数据类型 的一般要点:
您正确陈述:
The property [data type] changed to double
[...] i found out that Powershell's GenericMeasureInfo.Sum
property can only give back a Nullable<Double>
as property value.
确实:Measure-Object -Sum
:
- 总是 使用
[double]
值来汇总输入。
- 它 强制 输入到
[double]
s,如果可能的话——即使它们不是数字。
- 如果无法将输入强制转换为
[double]
(例如,'foo'
),则会发出非终止错误,但会继续对任何剩余输入求和。
以上暗示 甚至 字符串 是 Measure-Object -Sum
可接受的输入,因为它们将被转换为 [double]
在求和期间按需。
这意味着您可以直接使用 Import-Csv
命令 ,如以下示例所示(使用两个 [pscustomobject]
实例来模拟 Import-Csv
的输出):
PS> ([pscustomobject] @{ Points = '3.7' }, [pscustomobject] @{ Points = '1.2' } |
Measure-Object Points -Sum).Sum
4.9 # .Points property values were summed correctly.
71301.6000000006
[...] It seems like i'm producing an overflow of "double"
溢出 意味着超过了可以存储在 [double]
中的最大值,这是 (a) 不太可能的([double]::MaxValue
是 1.79769313486232E+308
,即大于 10 的 308 次方)和 (b) 会产生不同的症状;例如:
PS> ([double]::MaxValue, [double]::MaxValue | Measure-Object -Sum).Sum
∞ # represents positive infinity
然而,你做得到的是四舍五入错误,因为[double]
类型的内部 binary 表示,并不总是有精确的 decimal 表示,这会导致令人困惑的计算结果;例如:
PS> 1.3 - 1.1 -eq 0.2
False # !! With [double]s, 1.3 - 1.1 is NOT exactly equal to 0.2
有关详细信息,请参阅 https://floating-point-gui.de/
使用 [decimal]
值确实解决了这个问题,但请注意,这是以 较小范围 为代价的(实际上,您得到 28 位小数的精度 - 最大值的绝对值取决于小数点的位置;作为整数,它是 79,228,162,514,264,337,593,543,950,335
,即接近 8 * 1028).
如果你确实需要 [decimal]
s 的精度,你必须避免 Measure-Object
并自己做求和 .
在原始命令的上下文中,您可以使用 Sum
LINQ 方法:
[Linq.Enumerable]::Sum(
[decimal[]] @(
$Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender}
).Points
)
在管道命令周围使用 @(...)
(数组子表达式运算符)而不仅仅是 (...)
可确保在管道发生时整个命令不会失败至 return 无行 。 @(...)
将非输出变成 空数组 ,其中 .Sum()
正确 returns 0
.
- 没有它,
[decimal[]]
转换将导致 $null
,并且 PowerShell 将无法找到 .Sum()
方法的 [decimal[]]
类型重载并报错,"Multiple ambiguous overloads found for "Sum" and the argument count: 1".
上述命令总是需要将所有匹配的 CSV 行(表示为自定义对象)作为一个整体 放入内存中,而 Measure-Object
- 作为大多数PowerShell 管道中的 cmdlet - 将 一个接一个地处理它们 ,这只需要恒定数量的内存(但速度较慢)。
如果一次将所有匹配的行加载到内存中不是一个选项,请使用 ForEach-Object
(foreach
) cmdlet,但请注意,只有替换为实际的 Import-Csv
调用内存中的数组 $Imported_Csv
:
# 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 }
我遇到了以下情况:
我必须从 CSV 文件中获取信息。我使用 Import-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
的列是感兴趣的值 ("Points")。
我的第一个问题来了 --> 使用 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
?
提前致谢!
我首先将所有发件人地址分组在一起,然后分别对它们求和:
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
}
}
像 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
tl;dr:
如果您需要控制特定数值数据类型用于求和 数字:
避免
Measure-Object
,它总是使用[double]
计算。而是使用 LINQ
Sum
method(可在 PSv3+ 中访问)和 cast 到所需的数字类型:
[Linq.Enumerable]::Sum(
[decimal[]] @(
$Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender}
).Points
)
Points
column grouped by rows that share the same email address and [decimal]
值对其进行了改进。
一些 关于 Measure-Object
与 -Sum
和浮点数据类型 的一般要点:
您正确陈述:
The property [data type] changed to
double
[...] i found out that Powershell'sGenericMeasureInfo.Sum
property can only give back aNullable<Double>
as property value.
确实:Measure-Object -Sum
:
- 总是 使用
[double]
值来汇总输入。 - 它 强制 输入到
[double]
s,如果可能的话——即使它们不是数字。- 如果无法将输入强制转换为
[double]
(例如,'foo'
),则会发出非终止错误,但会继续对任何剩余输入求和。
- 如果无法将输入强制转换为
以上暗示 甚至 字符串 是 Measure-Object -Sum
可接受的输入,因为它们将被转换为 [double]
在求和期间按需。
这意味着您可以直接使用 Import-Csv
命令 ,如以下示例所示(使用两个 [pscustomobject]
实例来模拟 Import-Csv
的输出):
PS> ([pscustomobject] @{ Points = '3.7' }, [pscustomobject] @{ Points = '1.2' } |
Measure-Object Points -Sum).Sum
4.9 # .Points property values were summed correctly.
71301.6000000006
[...] It seems like i'm producing an overflow of "double"
溢出 意味着超过了可以存储在 [double]
中的最大值,这是 (a) 不太可能的([double]::MaxValue
是 1.79769313486232E+308
,即大于 10 的 308 次方)和 (b) 会产生不同的症状;例如:
PS> ([double]::MaxValue, [double]::MaxValue | Measure-Object -Sum).Sum
∞ # represents positive infinity
然而,你做得到的是四舍五入错误,因为[double]
类型的内部 binary 表示,并不总是有精确的 decimal 表示,这会导致令人困惑的计算结果;例如:
PS> 1.3 - 1.1 -eq 0.2
False # !! With [double]s, 1.3 - 1.1 is NOT exactly equal to 0.2
有关详细信息,请参阅 https://floating-point-gui.de/
使用 [decimal]
值确实解决了这个问题,但请注意,这是以 较小范围 为代价的(实际上,您得到 28 位小数的精度 - 最大值的绝对值取决于小数点的位置;作为整数,它是 79,228,162,514,264,337,593,543,950,335
,即接近 8 * 1028).
如果你确实需要 [decimal]
s 的精度,你必须避免 Measure-Object
并自己做求和 .
在原始命令的上下文中,您可以使用 Sum
LINQ 方法:
[Linq.Enumerable]::Sum(
[decimal[]] @(
$Imported_CSV | where {$_.Sender -eq $Imported_CSV_Unique.Sender}
).Points
)
在管道命令周围使用
@(...)
(数组子表达式运算符)而不仅仅是(...)
可确保在管道发生时整个命令不会失败至 return 无行 。@(...)
将非输出变成 空数组 ,其中.Sum()
正确 returns0
.- 没有它,
[decimal[]]
转换将导致$null
,并且 PowerShell 将无法找到.Sum()
方法的[decimal[]]
类型重载并报错,"Multiple ambiguous overloads found for "Sum" and the argument count: 1".
- 没有它,
上述命令总是需要将所有匹配的 CSV 行(表示为自定义对象)作为一个整体 放入内存中,而
Measure-Object
- 作为大多数PowerShell 管道中的 cmdlet - 将 一个接一个地处理它们 ,这只需要恒定数量的内存(但速度较慢)。
如果一次将所有匹配的行加载到内存中不是一个选项,请使用 ForEach-Object
(foreach
) cmdlet,但请注意,只有替换为实际的 Import-Csv
调用内存中的数组 $Imported_Csv
:
# 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 }