如何解决构建 Hashmap 的 Hashmap
我不经常问问题(大多数时候问题可以通过一些研究来解决,对吗?)但我只是想听听您的意见,因为可能有更好(更有效的方法)。
所以让我们看看,下面的代码运行得非常好,并且达到了它的目的。代码的结果是一个哈希图的哈希图,我需要它作为另一项工作的查找表。
背景:
-
$ccDb
是一个包含大约 200k 项的数组,属性为companyCd,costCenterNbr,costCenterShortNm,costCenterLongDescr
。 - 每个属性都必须修剪(请不要让我修剪我的数据库,遗憾的是我不能)。
-
costCenterNbr
包含在companyCd
上,这意味着每个companyCd
可以有多个costCenterNbr
。 -
companyCd
可以包含 X 个costCenterNbr
。 -
costCenterNbr
具有唯一值,companyCd
也是如此。 -
costCenterShortNm
和costCenterLongDescr
与costCenterNbr
相关
问题:
必须在每次运行我的脚本时构建此映射,因为信息是从 sql 表中获取的(它一直在变化)。构建此地图大约需要 15 分钟(在相当不错的服务器上,2cpu 12 核)。
问题:
您是否认为可以改进此代码以实现更快/更高效的执行?
$ccMap=@{}
foreach($line in $ccDb)
{
$companyCd=$line.companyCd.trim()
$costCenterNbr=$line.costCenterNbr.trim()
$costCenterShortNm=$line.CostCenterShortNm.trim()
$costCenterLongDescr=$line.CostCenterLongDescr.trim()
$coceMap=@{
$costCenterNbr=@{
shortDesc=$costCenterShortNm
longDesc=$costCenterLongDescr
}
}
if($ccMap.ContainsKey($companyCd))
{
$ccMap[$companyCd]+=$coceMap
}
else
{
$ccMap.Add($companyCd,$coceMap)
}
}
对于冗长的解释,我深表歉意,但我觉得最好预先提供最多的信息。很感谢任何形式的帮助。此外,我知道 PowerShell 是一种非常糟糕的语言,而 C# 可能会更有效率,但它就是这样。
编辑:添加测量值以供参考。
编辑:
非常感谢@Mathias R. Jessen,这是他的代码的测量结果。优秀的代码。
解决方法
不要在紧凑的循环中使用 +=
这是您最大的水槽:
$ccMap[$companyCd] += $coceMap
当您使用 +
(或 +=
)将一个哈希表添加到另一个时,PowerShell 会创建一个全新的哈希表:
# Create two different hashtables
$A = @{ Key1 = 'Value1' }
$B = @{ Key2 = 'Value2' }
# Let's save a second reference to the first table
$remember = $A
# Now let's use += to merge the two:
$A += $B
运行这个,你会发现 $B
和 $remember
没有变化,但是 $A
有两个键 - 因此必须是一个新的。
为了避免这种性能损失,完全跳过 $coceMap
的构造,并颠倒顺序(如果不存在,首先构造哈希表,然后分配):
$ccMap=@{}
foreach($line in $ccDb)
{
$companyCd=$line.companyCd.trim()
$costCenterNbr=$line.costCenterNbr.trim()
$costCenterShortNm=$line.CostCenterShortNm.trim()
$costCenterLongDescr=$line.CostCenterLongDescr.trim()
# Create new hashtable if none exist,otherwise retrieve the existing one
if($ccMap.ContainsKey($companyCd))
{
$coceMap = $ccMap[$companyCd]
}
else
{
$coceMap = $ccMap[$companyCd] = @{}
}
$coceMap[$costCenterNbr] = @{
shortDesc=$costCenterShortNm
longDesc=$costCenterLongDescr
}
}
基准测试 +=
以下是与 10000 项具有 50 个不同键的差异的简化示例:
$data = @(
1..10000 |Select-Object @{Name='Company';Expression={Get-Random -Maximum 50}},@{Name='CostCenter';Expression={Get-Random}}
)
@(
Measure-Command {
$map = @{}
foreach($line in $data){
$entry = @{
$line.CostCenter = @{
Value = 123
}
}
if($map.ContainsKey($line.Company)){
$map[$line.Company] += $entry
}
else {
$map[$line.Company] = $entry
}
}
}
Measure-Command {
$map = @{}
foreach($line in $data){
if($map.ContainsKey($line.Company)){
$entry = $map[$line.Company]
}
else {
$entry = $map[$line.Company] = @{}
}
$entry[$line.CostCenter] = @{
Value = 123
}
}
}
) |select TotalMilliseconds
我的笔记本电脑上的:
TotalMilliseconds
-----------------
306.4218
47.8164
一般如何识别这样的时间汇?
有多种方法可以分析 PowerShell 的运行时行为,但这是我个人的首选:
- 安装
PSProfiler
(免责声明:我是PSProfiler
的维护者):Install-Module PSProfiler -Scope CurrentUser
- 像使用
Measure-Script
一样使用Measure-Command
:
Measure-Script {
$map = @{}
foreach($line in $data){
$entry = @{
$line.CostCenter = @{
Value = 123
}
}
if($map.ContainsKey($line.Company)){
$map[$line.Company] += $entry
}
else {
$map[$line.Company] = $entry
}
}
}
- 等待代码完成
- 查看输出:
Anonymous ScriptBlock
Count Line Time Taken Statement
----- ---- ---------- ---------
0 1 00:00.0000000 {
1 2 00:00.0000187 $map = @{}
0 3 00:00.0000000
0 4 00:00.0000000 foreach($line in $data){
10000 5 00:00.0635585 $entry = @{
0 6 00:00.0000000 $line.CostCenter = @{
0 7 00:00.0000000 Value = 123
0 8 00:00.0000000 }
0 9 00:00.0000000 }
0 10 00:00.0000000
0 11 00:00.0000000 if($map.ContainsKey($line.Company)){
9950 12 00:00.3965227 $map[$line.Company] += $entry
0 13 00:00.0000000 }
0 14 00:00.0000000 else {
50 15 00:00.0002810 $map[$line.Company] = $entry
0 16 00:00.0000000 }
0 17 00:00.0000000 }
0 18 00:00.0000000 }
请注意,第 12 行占用了最多的总执行时间 - 明显多于其他任何行:
9950 12 00:00.3965227 $map[$line.Company] += $entry
,
我有两个建议:
您根本不需要 if
\ else
\ .add()
语句,powershell 会根据需要添加键。这应该会占用大部分时间,因为您不会为每个条目搜索整个表格:
$ccMap[$companyCd]+=$coceMap
如果您只使用一次该值,则不需要在上面设置变量。只需使用您的 $line
:
$coceMap=@{
$line.costCenterNbr.trim()=@{
shortDesc = $line.CostCenterShortNm.trim()
longDesc = $line.CostCenterLongDescr.trim()
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。