以数组为值对哈希表进行排序

Posted

技术标签:

【中文标题】以数组为值对哈希表进行排序【英文标题】:Sort Hashtable with Arrays as values 【发布时间】:2017-12-13 12:39:08 【问题描述】:

描述:我正在构建一个搜索文件的 PowerShell 脚本,然后给它们提供唯一的名称,复制它们,然后通过哈希计算验证它们 - 我选择将脚本拆分为函数每个步骤,因此更容易维护整个事情。 为了将所有值从一个函数传递到另一个函数,我选择使用[hashtable]$FooBar - 在$FooBar 内部,有多个数组,例如FullNameOutputPath(每个文件可能会更改,因为它们将被复制到名为yyyy-mm-dd 的子文件夹)。所有数组都相互关联(这意味着索引 1 包含第一个文件的所有值,索引 2 包含第二个文件的值,...),到目前为止,这工作正常。

一个简短的简化可视化:

$FooBar = @
$FooBar.FullName = @()
$FooBar.Size = @()
$FooBar.Ext = @()
Get-ChildItem | ForEach-Object 
    $FooBar.FullName += $_.FullName
    $FooBar.Size += $_.Length
    $FooBar.Ext += $_.Extension

但是,我现在需要按其中一个数组的一个值集对它们进行排序,例如规模。或者,再次可视化:

# From:
$FooBar
Name                           Value
----                           -----
fullname                       D:\AAA.XYZ, D:\BBB.ZYX, D:\CCC.YZX
size                           222, 111, 555
extension                      .XYZ, .ZYX, .YZX

# To:
$FooBar = $FooBar | Sort-Object -Property Size -Descending
$FooBar
Name                           Value
----                           -----
fullname                       D:\CCC.YZX, D:\AAA.XYZ, D:\BBB.ZYX
size                           555, 222, 111
extension                      .YZX, .XYZ, .ZYX

我试过$FooBar.GetEnumerator() | Sort-Object -Property Size,但这并没有改变任何东西。 Google 提出了有关如何对一组哈希表进行排序的建议,但在我的情况下,情况正好相反,我无法理解这一点,因为 我什至不明白为什么这是一个问题第一名

所以我的问题是:有没有办法通过其中一个数组的值集对哈希表中的所有数组进行排序?我无法理解这一点。

免责声明:我是一名 PowerShell 自学者,在脚本/编程方面没有合理的背景,所以我的“将所有内容包含在一个哈希表中”的解决方案很可能行不通完全没有,或者可能效率极低 - 如果是这样,请告诉我。

【问题讨论】:

为什么不将所有FileInfo 对象存储在一个数组中? 嗯,我猜原因归结为 a) 缺乏经验和 b) 预期的易用性(a))。我也没有对我的问题表达这种共鸣。我坐在这里试图理解/完全理解(并以测试方式实施)每一个建议,以便我可以相应地选择/投票 - 据我所知,你们似乎都比我更了解我的问题做 ;-) 。这个社区简直太棒了! 【参考方案1】:

我相信你正在尝试做的最简单的方法是Select-Object

$fooBar = Get-ChildItem | Select-Object FullName, Size, Extension

这将创建一个仅具有所需属性的新对象数组。这有效而您的方法无效的原因是因为 Sort-Object 适用于属性,而您指定的属性位于几层之后。

如果您需要更多的灵活性而不仅仅是精确的属性,您可以像这样创建自己的

$fooBar = Get-ChildItem | Select-Object @Name = 'SizeMB'; Expression = $_.Size / 1MB

或者使用[PSCustomObject] 类型加速键手动创建新属性:

$fooBar = Get-ChildItem | ForEach-Object 
    [PSCustomObject]@
        FullName = $_.FullName
        Extension = $_.Extension
        Size = $_.Size
    

更新

如果您需要在对象最初创建后为其添加其他属性,您有几个选择。

添加成员

目前最常用的方法是使用Add-Member cmdlet。

$object | Add-Member -MemberType NoteProperty -Name NewProperty -Value 'MyValue'

$object

需要记住的重要一点是,默认情况下,此 cmdlet 不返回任何内容。因此,如果您将上述语句放在函数的末尾并且不单独返回对象,那么您的函数将不会返回任何内容。确保您使用-PassThru 参数(这对于链接Add-Member 命令也很有用)或之后调用变量(如上面的示例)

选择对象

使用计算属性添加成员时,您可以选择所有以前的属性。请记住,由于Select-Object 的工作方式,源对象中的所有方法都不会被继承。

$fooBar | Select-Object *, @Name = 'NewProperty'; Expression = 'MyValue'

psobject.Properties

这是我个人最喜欢的一个,但仅限于更高版本的 PowerShell,我还没有真正看到其他人使用它。

$fooBar.psobject.Properties.Add([psnoteproperty]::new('NewProperty', 'MyValue'))
$fooBar

每个成员类型都有自己的构造函数。您还可以将方法添加到 $fooBar.psobject.Methods 或键入到 $fooBar.psobject.Members。我喜欢这种方法,因为它感觉更明确,而且添加成员和成员的感觉是正确的。

总结

您选择的方法主要是偏好。如果可能的话,我会推荐Add-Member,因为它是最常用的,因此具有更好的可读性和更多的人可以回答有关它的问题。

我还想提一下,如果可能的话,通常最好避免添加其他成员。理想情况下,函数的返回值应该具有可靠的形式。如果有人在使用您的函数并且他们必须猜测属性或方法何时会存在于您的对象上,则调试变得非常困难。显然这不是一个硬性规定,但如果您需要添加一个成员,您至少应该考虑是否更好地重构。

【讨论】:

之所以选为答案,是因为它确实以一种易于理解的语言解释了一切。我只有一件事不明白:我可以在这个函数之外再添加[PSCustomObject]@s 吗?由于我的函数的预期模块化,我希望之后能够创建/添加属性到$fooBar。但是,我无法使其正确正常工作(我尝试了Add-Member 并使用其他变量重复您的上一个脚本)。很抱歉,我知道这很可能超出了我的问题范围,但在我看来,这确实会让你的答案变得更好。 @flolilolilo 我认为肯定在原始问题的范围内:) 我添加了一个关于在初始创建后添加属性的部分。如果有任何不清楚的地方,请告诉我。 非常感谢!只是一个小提示:Add-Member 似乎使用-Name,而不是-MemberName。 (Add-Member:找不到与参数名称“MemberName”匹配的参数。)但这是我能提出的唯一批评。 :-) 确实是 :) 我匆忙将它从 -NotePropertyName-NotePropertyValue 更改为兼容更多版本,忘记仔细检查!谢谢你告诉我,它现在更新了。【参考方案2】:

出于所有实际目的,我强烈建议您将所需的对象存储在单个数组中,排序一次,然后在需要时引用每个对象的各个属性:

$FooBar = Get-ChildItem |Sort-Object -Property Length

# Need the Extension property of the object at index 4?
$FooBar[4].Extension

回答您的实际问题:

Array.Sort() has an overload 分别采用键和值数组。您可以为要排序的每个其他属性复制要排序的数组:

# Create hashtable of correlated arrays 
$FooBar = @
$FooBar.FullName = @()
$FooBar.Size = @()
$FooBar.Ext = @()
# Types cast explicitly to avoid Array.Sort() calling .CompareTo() on the boxing object
Get-ChildItem | ForEach-Object 
    $FooBar.FullName += [string]$_.FullName
    $FooBar.Size     += [int]$_.Length
    $FooBar.Ext      += [string]$_.Extension


# Define name of reference array property
$SortKey = 'Size'

# Sort all arrays except for the reference array
$FooBar.Keys |Where-Object $_ -ne $SortKey |ForEach-Object 
    # Copy reference values to new array
    $Keys = $FooBar[$SortKey].Clone()

    # Sort values in target array based on reference values
    [array]::Sort($Keys,$FooBar[$_])


# Finally sort the reference array
[array]::Sort($FooBar[$SortOn])

上述方法仅在引用数组由值类型组成时才有效

【讨论】:

【参考方案3】:

PowerShell 让处理对象变得异常简单。

试试:

$FooBar = Get-Childitem
$FooBar | Get-Member

这将告诉您$Foobar 实际上包含FileInfoDirectoryInfo 类型的对象,并显示Properties 可用。

$FooBarSortedBySizeDesc  = $FooBar | Sort-Object Length -Descending
$FooBarFullNamesOnly = $FooBar.FullName

【讨论】:

以上是关于以数组为值对哈希表进行排序的主要内容,如果未能解决你的问题,请参考以下文章

Powershell 排序哈希表

c# 按键排序的哈希表

JAVA笔记(16)---集合- 详解 Set集合( Map 体系集合常用方法;哈希表;二叉树数据结构;Map集合,如何自定义集合排序规则 )

《图解算法》--快速排序哈希表图广度优先搜索算法

SortedList排序列表

源码:Java集合源码之:哈希表