【问题标题】:In Powershell, how do I sort a Collections.Generic.List of DirectoryInfo?在 Powershell 中,如何对 DirectoryInfo 的 Collections.Generic.List 进行排序?
【发布时间】:2021-05-03 17:59:18
【问题描述】:

我想要一个包含与 subjectPattern 匹配的文件的唯一目录的列表。 我可以获取列表,但要获取唯一目录,我需要对其进行排序。但是因为列表是 Collections.Generic.List[DirectoryInfo] 类型的,我无法找到有效的 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] 中获取唯一项目?

【问题讨论】:

  • 将元素附加到列表的正确方法是通过其Add() 方法-> fatList.Add((Get-Item $leafName).Directory)+= 添加集合成员效率低下。您可以对对象进行分组,然后排序 --> $fatList | Group FullName |% { $_.Group[0] } | Sort Name。我不知道这是否是你的目标。如果您的叶子项目没有 Directory 属性,则列表中的空值将在输出中不可见。
  • 出于某种原因就地排序是否重要?您专门使用通用列表是否重要?
  • 不,这个函数的任何细节对我来说都不重要,除了它返回一个唯一的 DirectorInfo 集合。所以不需要就地排序,或者是一个列表而不是一个 HashObject。谢谢!

标签: list powershell sorting unique directoryinfo


【解决方案1】:

来自您的内联 cmets:

[...] Sort-Object 没有属性将无法工作,但“FullName”不是 Collections.Generic.List 的属性

没关系,我们不是对多个列表进行排序,而是对恰好包含在一个列表中的多个 DirectoryInfo 对象进行排序。

最大的问题是:您需要就地排序吗?

“就地”排序意味着重新排列列表内部的对象,以便列表本身保留新的排序顺序它的标识。这通常占用较少的资源,但在 PowerShell 中稍微复杂一些。

另一种方法是枚举列表中的项目,在外部对它们进行排序,然后(可选)将重新排序的项目包装在 new 列表中 - 更容易实施,但要付出资源成本(您可能会注意到也可能不会注意到,具体取决于集合的大小和比较的复杂性)。

就地排序

为了对多个 DirectoryInfo 对象进行排序,我们需要一种方法来指示 List[DirectoryInfo].Sort() 方法如何将对象相互比较并确定在排序顺序中哪个在另一个之前或之后。

查看Sort() 方法重载给了我们一个线索:

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)

所以我们需要实现通用接口IComparer[T]的东西。

利用 PowerShell 在运行时使用 class 关键字定义新类型的能力,我们可以做到:

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

# 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

外部排序

现在我们知道了对通用集合进行就地排序所必须经历的试验,让我们回顾一下从外部对集合进行排序的过程:

$sorted = $list |Sort-Object FullName

如果我们需要生成的(现已排序的)集合也是 [List[Directory]] 类型,我们可以清除并重新填充原始列表:

$list.Clear()
$sorted |ForEach-Object {$list.Add($_)}

...或者我们可以创建一个新的[List[DirectoryInfo]] 实例:

$list = [List[DirectoryInfo]]::new([DirectoryInfo[]]$sorted)

SortedSet[DirectoryInfo] 怎么样?

作为already suggested,“集合”可能是更好的集合类型,仅用于存储唯一项。

HashSet[T] 类型是一个 无序 集合,但 .NET 也带有一个 SortedSet[T] type - 你不会相信实现排序顺序需要什么 em> - 没错,IComparer[T]! :-)

在这种情况下,我们希望在创建集合时将比较器注入到构造函数中:

# 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() 对其进行排序并不完全清楚,所以我的建议是对它们进行全部测试,看看什么最适合你

【讨论】:

    【解决方案2】:

    尝试改变:

    $fatList = New-Object Collections.Generic.List[System.IO.DirectoryInfo]
    

    到一个 HashSet,它只允许唯一值。

    $fatList = New-Object Collections.Generic.Hashset[System.IO.DirectoryInfo] 
    

    并注释掉:

    #$result = ($fatList.Sort() | Get-Unique )
    

    @AdminOfThings 是正确的,使用 .Add() 方法。 https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.hashset-1.add?view=net-5.0。另外,您确定要使用 Directory,而不是 DirectoryName?

    编辑:我刚刚意识到我没有完全回答这个问题。将 HashSet 变量设置为无序项(默认行为)后,将其通过管道传递给 Sort-Object cmdlet。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-30
      • 1970-01-01
      • 1970-01-01
      • 2019-04-25
      • 2013-12-06
      • 2023-02-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多