在Powershell中,如何对DirectoryInfo的Collections.Generic.List进行排序? [英] In Powershell, how do I sort a Collections.Generic.List of DirectoryInfo?
问题描述
我想要一个包含与subjectPattern匹配的文件的唯一目录的列表.我可以获取列表,但是要获取唯一目录,我需要对其进行排序.但是因为列表是类型为Collections.Generic.List [DirectoryInfo],我很难找到有效的API.
I want a list of the unique directories that contain a file matching subjectPattern. I can get the list, but to get the unique directories, I need to sort it. But because the list is of type Collections.Generic.List[DirectoryInfo], I'm having trouble finding a working API.
function Get-Containers([Parameter(Mandatory)][string]$subjectPattern) {
#NOTE: The class for directories is System.IO.DirectoryInfo, the class for files is System.IO.FileInfo
$fatList = New-Object Collections.Generic.List[System.IO.DirectoryInfo]
$result = New-Object Collections.Generic.List[System.IO.DirectoryInfo]
foreach ($leafName in (get-childitem -recurse -force -path . -include $subjectPattern)) {
$fatList += (Get-Item $leafName).Directory
}
#Get-Unique only works on sorted collections, Sort-Object won't work without a Property,
# but "FullName" is not a property of Collections.Generic.List
# Furthermore, Sort() is not a method of [System.IO.DirectoryInfo]
$result = ($fatList.Sort() | Get-Unique )
return $result
}
如何排序,然后在Collections.Generic.List [System.IO.DirectoryInfo]中获得唯一项?
How do I sort, then get unique items in Collections.Generic.List[System.IO.DirectoryInfo] ?
推荐答案
内嵌评论:
[...]
排序对象
在没有属性的情况下不起作用,但是"FullName"不是Collections.Generic.List
[...]
Sort-Object
won't work without a Property, but "FullName" is not a property of Collections.Generic.List
很好,我们不是对多个列表进行排序,而是对多个恰好包含在单个列表中的 DirectoryInfo
对象进行排序.
That's fine, we're not sorting multiple lists, we're sorting multiple DirectoryInfo
objects that happen to be contained in a single list.
最大的问题是:您需要就地排序吗?
将就地"排序表示将对象重新布置在列表内,以便列表本身保留新的排序顺序和.这通常不占用大量资源,但在PowerShell中则稍微复杂一些.
Sorting "in-place" means re-arranging the objects inside the list, so that the list itself retains the new sort order and its identity. This is usually less resource-intensive, but slightly complicated in PowerShell.
另一种方法是枚举列表中的项目,在外部对其进行排序,然后(可选)将重新排序的项目包装在 new 列表中-容易得多实施,但要付出一定的资源成本(您可能会注意到,也可能不会注意到,这取决于学院的规模和比较的复杂程度).
The alternative is to enumerate the items in the list, sort them externally and then (optionally) wrapping the re-ordered items in a new list - much easier to implement, but at a resource cost (which you may or may not notice depending on the size of the colleciton and the complexity of the comparison).
为了对多个 DirectoryInfo
对象进行排序,我们需要一种方法来指示 List [DirectoryInfo] .Sort()
方法如何将对象彼此进行比较并按排序顺序确定哪个在另一个之前或之后.
In order to sort multiple DirectoryInfo
objects, we need a way to instruct the List[DirectoryInfo].Sort()
method on how to compare the objects to each other and determine which comes before or after the other in the sort order.
查看 Sort()
方法重载可以为我们提供一个线索:
Looking at the Sort()
method overloads gives us a clue:
PS ~> $list = [System.Collections.Generic.List[System.IO.DirectoryInfo]]::new()
PS ~> $list.Sort
OverloadDefinitions
-------------------
void Sort()
void Sort(System.Collections.Generic.IComparer[System.IO.DirectoryInfo] comparer)
void Sort(int index, int count, System.Collections.Generic.IComparer[System.IO.DirectoryInfo] comparer)
void Sort(System.Comparison[System.IO.DirectoryInfo] comparison)
So we need something that implements the generic interface IComparer[T]
.
利用PowerShell在运行时使用 class
关键字定义新类型的能力,我们可以做到:
Using PowerShell's ability to define new types at runtime using the class
keyword, we can do:
using namespace System.Collections.Generic
using namespace System.IO
class DirectoryInfoComparer : IComparer[DirectoryInfo]
{
[string]$PropertyName
[bool]$Descending = $false
DirectoryInfoComparer([string]$property)
{
$this.PropertyName = $property
}
DirectoryInfoComparer([string]$property, [bool]$descending)
{
$this.PropertyName = $property
$this.Descending = $descending
}
[int]Compare([DirectoryInfo]$a, [DirectoryInfo]$b)
{
$res = if($a.$($this.PropertyName) -eq $b.$($this.PropertyName))
{
0
}
elseif($a.$($this.PropertyName) -lt $b.$($this.PropertyName))
{
-1
}
else
{
1
}
if($this.Descending){
$res *= -1
}
return $res
}
}
...,现在我们可以根据属性名称对列表进行排序,就像使用 Sort-Object
:
... and now we can sort the list in-place based on a property name, just like with Sort-Object
:
# Create a list
$list = [List[DirectoryInfo]]::new()
# Add directories in non-sorted order
mkdir c,a,b -Force |ForEach-Object { $list.Add($_) }
# Instantiate a comparer based on the `FullName` property
$fullNameComparer = [DirectoryInfoComparer]::new("FullName")
# Now sort the list
$list.Sort($fullNameComparer)
# Observe that items are now sorted based on FullName value
$list.FullName
从外部排序
现在,我们知道就地对通用集合进行分类所必须经历的试验,让我们回顾一下从外部对集合进行分类的过程:
Sort externally
Now that we know the trials we must go through to sort a generic collection in-place, let's review the process of sorting the collection externally:
$sorted = $list |Sort-Object FullName
如果我们需要结果(现在已排序)的集合也必须是 [List [Directory]]
类型,我们可以清除并重新填充原始列表:
If we need the resulting (now sorted) collection to also be of type [List[Directory]]
, we can either clear and re-populate the original list:
$list.Clear()
$sorted |ForEach-Object {$list.Add($_)}
...或者我们可以创建一个新的 [List [DirectoryInfo]]
实例:
... or we can create a new [List[DirectoryInfo]]
instance:
$list = [List[DirectoryInfo]]::new([DirectoryInfo[]]$sorted)
SortedSet [DirectoryInfo]
怎么样?
已建议,即设置"出于仅存储唯一项的目的,可能是一种更好的收集类型.
How about a SortedSet[DirectoryInfo]
?
As already suggested, a "set" might be a better collection type for the purpose of only storing unique items.
HashSet [T]
类型是 unordered 集,但是.NET也带有
The HashSet[T]
type is an unordered set, but .NET also comes with a SortedSet[T]
type - and you won't believe what it requires to implement the sort order - that's right, an IComparer[T]
! :-)
在这种情况下,我们将在创建集合时将比较器注入到构造函数中:
In this case, we'll want to inject the comparer into the constructor when we create the set:
# Once again, we need an IComparer[DirectoryInfo] instance
$comparer = [DirectoryInfoComparer]::new("FullName")
# Then we create the set, injecting our custom comparer
$set = [System.Collections.Generic.SortedSet[System.IO.DirectoryInfo]]::new($comparer)
# Now let's add a bunch of directories in completely jumbled order
Get-ChildItem -Recurse -Directory |Select -First 10 |Sort {Get-Random} |ForEach-Object {
# The Add() method emits a boolean indicating whether the item
# is unique or already exists in the set, hence the [void] cast
[void]$set.Add()
}
# Once again, observe that enumerating the set emits the items sorted
$set.FullName
如您所见,有多个可用选项,它们具有不同程度的复杂性和性能特征.从您的问题为什么您使用的是通用列表还是为什么您坚持使用 List.Sort()
对其进行排序的原因尚不完全清楚,所以我的建议是测试他们全力以赴,看看最适合您的
As you can see, there are multiple options available, with varying degrees of complexity and performance characteristics. It's not entirely clear from your question why you're using a generic list or why you insist on sorting it using List.Sort()
, so my recommendation would be to test them all out and see what works best for you
这篇关于在Powershell中,如何对DirectoryInfo的Collections.Generic.List进行排序?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!