我处于以下情况:
我必须从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
吗?
谢谢!
答案 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
值来汇总输入。[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-Object
(ForEach-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
}
}