以数组为值对哈希表进行排序
Sort Hashtable with Arrays as values
描述:我正在构建一个搜索文件的 PowerShell 脚本,然后为它们提供唯一的名称,复制它们,然后通过哈希计算验证它们 - 我选择了将脚本拆分为每个步骤的函数,这样更容易维护整个过程。
为了从一个函数获取所有值到另一个函数,我选择使用 [hashtable]$FooBar
- 在 $FooBar
中,有多个数组,例如 FullName
或 OutputPath
(可能会改变每个文件,因为它们将被复制到名为 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 自学者,在 scripting/programming 方面没有合理的背景,所以我的 "include everything in one hashtable" 解决方案很可能是根本无法工作或可能效率极低 - 如果是这样,请告诉我。
完成我认为您正在尝试做的事情的最简单方法是 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 不会 return 任何内容。因此,如果您将上述语句放在函数的末尾并且不单独 return 对象,则您的函数将不会 return 任何东西。确保使用 -PassThru
参数(这对于链接 Add-Member
命令也很有用)或之后调用变量(如上例)
Select-对象
在使用计算属性添加成员时,您可以 select 所有以前的属性。请记住,由于 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
,因为它是最常用的,因此具有更好的可读性和更多的人可以回答有关它的问题。
我还想提一下,通常最好尽可能避免添加额外的成员。理想情况下,函数的 return 值应该具有可靠的形式。如果有人在使用您的函数并且他们必须猜测 属性 或方法何时会存在于您的对象上,那么调试将变得非常困难。显然这不是一个硬性规定,但如果您需要添加一个成员,您至少应该考虑是否重构会更好。
PowerShell 使处理对象变得异常简单。
尝试:
$FooBar = Get-Childitem
$FooBar | Get-Member
这将告诉您 $Foobar
实际上包含 FileInfo
和 DirectoryInfo
类型的对象,并向您显示可用的 Properties
。
$FooBarSortedBySizeDesc = $FooBar | Sort-Object Length -Descending
$FooBarFullNamesOnly = $FooBar.FullName
出于所有实际目的,我强烈建议您只将所需的对象存储在一个数组中,对其进行一次排序,然后在需要时引用每个对象的各个属性:
$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])
以上仅适用于引用数组由值类型组成的情况
描述:我正在构建一个搜索文件的 PowerShell 脚本,然后为它们提供唯一的名称,复制它们,然后通过哈希计算验证它们 - 我选择了将脚本拆分为每个步骤的函数,这样更容易维护整个过程。
为了从一个函数获取所有值到另一个函数,我选择使用 [hashtable]$FooBar
- 在 $FooBar
中,有多个数组,例如 FullName
或 OutputPath
(可能会改变每个文件,因为它们将被复制到名为 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 自学者,在 scripting/programming 方面没有合理的背景,所以我的 "include everything in one hashtable" 解决方案很可能是根本无法工作或可能效率极低 - 如果是这样,请告诉我。
完成我认为您正在尝试做的事情的最简单方法是 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 不会 return 任何内容。因此,如果您将上述语句放在函数的末尾并且不单独 return 对象,则您的函数将不会 return 任何东西。确保使用 -PassThru
参数(这对于链接 Add-Member
命令也很有用)或之后调用变量(如上例)
Select-对象
在使用计算属性添加成员时,您可以 select 所有以前的属性。请记住,由于 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
,因为它是最常用的,因此具有更好的可读性和更多的人可以回答有关它的问题。
我还想提一下,通常最好尽可能避免添加额外的成员。理想情况下,函数的 return 值应该具有可靠的形式。如果有人在使用您的函数并且他们必须猜测 属性 或方法何时会存在于您的对象上,那么调试将变得非常困难。显然这不是一个硬性规定,但如果您需要添加一个成员,您至少应该考虑是否重构会更好。
PowerShell 使处理对象变得异常简单。
尝试:
$FooBar = Get-Childitem
$FooBar | Get-Member
这将告诉您 $Foobar
实际上包含 FileInfo
和 DirectoryInfo
类型的对象,并向您显示可用的 Properties
。
$FooBarSortedBySizeDesc = $FooBar | Sort-Object Length -Descending
$FooBarFullNamesOnly = $FooBar.FullName
出于所有实际目的,我强烈建议您只将所需的对象存储在一个数组中,对其进行一次排序,然后在需要时引用每个对象的各个属性:
$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])
以上仅适用于引用数组由值类型组成的情况