从阵列中删除“较少”条目

时间:2018-10-20 01:33:41

标签: powershell

我正在尝试修剪一些我拥有的文件。我将为您节省到目前为止所写的野兽,并通过提供一个虚构的代码来简化它。

让我们看一下这个数组:

[System.String[]]$Collection = 'Invitational.Gold.txt', 'Invitational.Bronze.txt', 'Invitational.Silver.txt', 'Olympics.Silver.txt', 'Olympics.Bronze.txt'

这时,我需要做三件事:

  1. 按匹配类型(应试/奥运会)分组
  2. 按奖牌类型按值的降序排列(金/银/青铜
  3. 对于每种比赛类型,保留最高价值的奖牌,同时删除其他奖牌。

我的第一个想法是使用一些RegEx:

$Collection[0] -match '^(.+)\.(bronze|silver|gold).txt'

我在这里使用[0],因此无需编写foreach就可以对其进行测试。

以上内容将Invitational存储在$Matches[1]中,并将Gold存储在$Matches[2]中。

现在,我可以使用$Collection按匹配类型过滤Where-Object。但是然后我在步骤3中遇到了问题。您看到,如果我使用一个foreach-construct,它将执行match 3次,并获得3次金牌。而且由于它是第一次删除文件,因此在第二次和第三次运行期间会产生错误。

那么有人可以解释我怎样才能对每个比赛类型进行一次查找?换句话说:

  1. 它找到第一个文件:Invitational.Gold
  2. 它看起来是否还有来自Invitational的奖牌,并找到了已删除的银牌和铜牌。
  3. 它将继续移动到内存中的下一个文件Invitational.Bronze.txt 它看起来是从Invitational看是否还有其他奖牌,并找到了银牌和金牌。继续尝试再次删除青铜和银,导致错误,因为它们已被删除。这就是我需要避免但一直无法避免的事情。

那么我该如何做到这一点,同时又避免了重复的代码块(在每次操作之后重新编写$Collections内容)?

我能想到的最巧妙的解决方案是以某种方式使用Group-Object为每个匹配类型创建组,然后每个组仅处理一次。但是我不知道该怎么做。

编辑: 我现在正在考虑以下方面:

$Result = $Collection | ForEach-Object -Process {
  $Null = $PSItem -match '^(.+)\.(bronze|silver|gold).txt'
  $Properties = @{
    'MatchType' = $Matches[1]
    'Object'    = $PSItem
  }
  New-Object -TypeName PSObject -Property $Properties
}
$Result | Group-Object -Property MatchType

现在我可以进行分组了。我认为。我会耐心等待更好的/其他建议。

4 个答案:

答案 0 :(得分:1)

这就是我的想法。但是可能会有一种更快的方法...但是重复此特定的100次总共花了36毫秒...

 [System.String[]]$Collection = 'Invitational.Gold.txt', 'Invitational.Bronze.txt', 'Invitational.Silver.txt', 'Olympics.Silver.txt', 'Olympics.Bronze.txt'

$collectionToOrder = $null
$collectionToOrder = foreach ($item in $Collection) {
$order = $null

    if ($item -match "Invitational") {$order = "1" }
    if ($item -match "Olympics") {$order = "2" }
    if ($item -match "Gold") {$order += "a" }
    if ($item -match "Silver") {$order += "b" }
    if ($item -match "Bronze") {$order += "c" }

    [pscustomobject] @{Order=$order
                       list=$Item
                                            }


}

$collectionOrdered = $collectionToOrder | sort -property order
Remove-Variable collectionToOrder 
$collectionToDelete = ($collectionOrdered | where order -Match "1")[1..5]
$collectionToDelete += ($collectionOrdered | where order -Match "2")[1..5]
foreach ($item in $collectionToDelete) {remove-item $item.list}

可以稍作修改(取决于您想要的输出),但是基本上我使第一组搜索需求添加了一个名为Order的属性,其值为1,2,依此类推...然后添加了下一组到属性a,b,c等。然后您可以继续添加到文本中。这意味着它可能有第三个或第四个理由要添加到订单中。最后按顺序排序问题是,如果它找到不同的匹配词(例如,silver.gold.Olympics),它将时髦地添加到order属性中,但是您的示例并没有将此作为可能的问题。

添加了另一部分以删除错误的文件。请注意,它会从当前工作目录中删除。

您最终可以做些类似的事情

foreach ($item in $collectionToDelete) {remove-item $folder\$item.list}

如果在顶部将$ folder变量设置为您要工作的地方

为时已晚,我重新编写它以使其工作方式有所不同,它不在乎您使用的是什么单词...

 [System.String[]]$Collection =  'Invitational.Bronze.txt', 'Invitational.Silver.txt',  'Invitational.Gold.txt', 'Olympics.Bronze.txt', 'Olympics.Silver.txt'

foreach ($item in $Collection) {
    if ($item -match "Silver") {
        remove-item $item.replace('.Silver.txt','.Bronze.txt')
    } 
}

foreach ($item in $Collection) {
    if ($item -match "Gold") {
        remove-item $item.replace('.Gold.txt','.Silver.txt')
        remove-item $item.replace('.Gold.txt','.Bronze.txt')
    }
}

这已经越来越长了,哈哈...无论如何,我回答了你,(是哪个天才)并简化了...

[System.String[]]$Collection = 'Invitational.Gold.txt', 'Invitational.Bronze.txt', 'Invitational.Silver.txt', 'Olympics.Silver.txt', 'Olympics.Bronze.txt', 'World.Open.Silver.txt'

$Collection | ForEach-Object -Process {
  If ($PSItem -match 'Silver') {
    $name = ($_ -split 'Silver') -join 'Bronze'
    If ($Collection -contains $name) { "Removing: $Name" }
  } # If 'Silver'
} # ForEach-Object

$Collection | ForEach-Object -Process {
  If ($PSItem -match 'Gold') {
      $name = ($_ -split 'Gold') -join 'Silver'
      If ($Collection -contains $name) { "Removing: $Name" }
      $name = ($_ -split 'Gold') -join 'Bronze'
      If ($Collection -contains $name) { "Removing: $Name" }
  } # If 'Gold'
} # ForEach-Object

答案 1 :(得分:1)

要以特定方式对值进行排序,则需要实现索引的排序。

$MedalValue = @{
    Gold = 3;
    Silver = 2;
    Bronze = 1;
}

[System.String[]]$Collection = 'Invitational.Gold.txt', 'Invitational.Bronze.txt', 'Invitational.Silver.txt', 'Olympics.Silver.txt', 'Olympics.Bronze.txt'

$DataSet = foreach ($Item in $Collection){
    $File = [regex]::Split($Item,'\.')
    New-Object PSObject -Property @{
        Type = $File[0];
        Medal = $File[1];
        Value = $MedalValue[$File[1]];
    }
}
$DataSet | Sort-Object @{expression='Type';Ascending=$true},@{expression='Value';Descending=$true} | Select-Object Type, Medal

输出:

Type         Medal 
----         ----- 
Invitational Gold  
Invitational Silver
Invitational Bronze
Olympics     Silver
Olympics     Bronze

不确定我是否完全理解您的问题。但是,如果您想获得每种类型的头条信息,则可以使用选择和排序,如下所示:

$DataSet | 
    Group-Object Type | 
        ForEach-Object {
            $Name = $_.Name
            $DataSet | 
                Where-Object {$_.Type -eq $Name} |
                    Sort-Object -Property Value -Descending |
                        Select @{Label='Files';Expression={'{0}.{1}.txt' -f $_.Type,$_.Medal}} -First 1
        }

输出:

Files                 
----                 
Invitational.Gold.txt
Olympics.Silver.txt  

答案 2 :(得分:1)

我尝试了一种稍微不同的方法。 [咧嘴]

按“ $ _。Split('。')[0]”分组,检查是否有金/银/青铜,然后使用IF / ELSEIF删除之后的项目。

这可能需要检查“删除”文件或将cmdlet设置为忽略“未找到”错误。

yourfile_2.csv

希望有帮助,

答案 3 :(得分:0)

使用给出的三个答案中的每一个之后,我在每种情况下都创建了一个可行的解决方案,试图在尽可能适用于我的情况的同时尽可能地靠近解决方案。这些是我想出的:

@Kirill Pashkov介绍了权重/价值的概念,我发现使用它非常有趣:

[System.String[]]$Collection = 'Invitational.Gold.txt', 'Invitational.Bronze.txt', 'Invitational.Silver.txt', 'Olympics.Silver.txt', 'Olympics.Bronze.txt', 'World.Open.Silver.txt'

$Weight = @{
  'Gold'   = 1;
  'Silver' = 2;
  'Bronze' = 3;
} # Weight

$DataSet = $Collection | ForEach-Object -Process {
  $Null = $PSItem -match '^(.+)\.(bronze|silver|gold).txt'
  $Properties = @{
    'Type'   = $Matches[1]
    'Medal'  = $Matches[2]
    'Weight' = $Weight[$Matches[2]]
    'Name'   = $PSItem
  } # Properties
  New-Object -TypeName PSObject -Property $Properties
} # ForEach-Object

$DataSet | Group-Object -Property 'Type' | ForEach-Object -Process {
  $PSItem.Group | Sort-Object -Property 'Weight' -Descending | Select-Object -SkipLast 1 | ForEach-Object -Process {
    Write-Output -InputObject ('Removing: {0}' -f $PSItem.Name)
  } # ForEach-Object
} # ForEach-Object

输出:

Removing: Invitational.Bronze.txt
Removing: Invitational.Silver.txt
Removing: Olympics.Bronze.txt

@Lee_Dailey成功地将Group-By放在了一条直线上。我无法解决这个问题,但是我使用了其他答案将其缩减为三行。然后,我不得不重新构造结果表,以根据不需要的文件的字符串值来过滤它们。而且我必须避免只包含一个文件的组。使用break,我可以防止来自Switch语句的重复结果:

[System.String[]]$Collection = @(
    'Invitational.Gold.txt'
    'Invitational.Bronze.txt'
    'Invitational.Silver.txt'
    'Olympics.Silver.txt'
    'Olympics.Bronze.txt'
    'World.Open.Silver.txt'
) # $Collection

$DataSet = $Collection | ForEach-Object -Process {
  $Null = $PSItem -match '^(.+)\.(bronze|silver|gold).txt'
  [PSCustomObject] @{'Type' = $Matches[1]; 'Object' = $PSItem}
}
$GroupedCollection = $DataSet | Group-Object -Property 'Type'

ForEach ($Grouping in $GroupedCollection) {
  If ($Grouping.Count -gt 1) {
    $GP_Item = [PSCustomObject]@{'Name' = $Grouping.Name; 'Group' = ($Grouping.Group | Select-Object -ExpandProperty 'Object')}

    Switch -Regex ($GP_Item.Group) {
      'gold' {
        "Keeping: $($GP_Item.Group -match 'gold')"
        "Removing: $($GP_Item.Group -match 'silver')"
        "Removing: $($GP_Item.Group -match 'bronze')"
        Break
      } # Gold
      'silver' {
        "Keeping: $($GP_Item.Group -match 'silver')"
        "Removing: $($GP_Item.Group -match 'bronze')"
        Break
      } # Silver
    } # Switch
  } # If
} # ForEach

输出:

Keeping: Invitational.Gold.txt
Removing: Invitational.Silver.txt
Removing: Invitational.Bronze.txt
Keeping: Olympics.Silver.txt
Removing: Olympics.Bronze.txt

最后,@ Robert Cotterman使用字符串操作和RegEx获得了一个非凡的解决方案,以获得所需的结果。我不得不重新编写代码以说明匹配类型未知,但是设法通过大量的管道使用使它最终运行:

[System.String[]]$Collection = 'Invitational.Gold.txt', 'Invitational.Bronze.txt', 'Invitational.Silver.txt', 'Olympics.Silver.txt', 'Olympics.Bronze.txt', 'World.Open.Silver.txt'

[System.Int16]$Count = 1
$MatchTypes = $Collection | ForEach-Object -Process {
  $Null = $PSItem -match '^(.+)\.(bronze|silver|gold).txt'
  $Matches[1]
} | Select-Object -Unique | ForEach-Object -Process {
  [PSCustomObject]@{'Name' = $PSItem; 'Order' = $Count}
  $Count += 1
}

$CollectionToOrder = ForEach ($Item in $Collection) {
  ForEach ($Type in $MatchTypes) { If ($Item -match $Type.Name) {[System.Int16]$Group = $Type.Order} }
  Switch -Regex ($Item) {
    'Gold'   { $Weight = 'a' }
    'Silver' { $Weight = 'b' }
    'Bronze' { $Weight = 'c' }
  } # Switch
  [PSCustomObject]@{ 'Group' = $Group; 'Weight' = $Weight; 'List' = $Item }
} # ForEach

$CollectionOrdered = $CollectionToOrder | Sort-Object -Property 'Group', 'Weight' -Descending
[System.Int16]$TypeCount = ($CollectionOrdered | Select-Object -Property 'Group' -Unique).Count
$CollectionToDelete = 1..$TypeCount | ForEach-Object -Process { $CollectionOrdered | Where-Object -Property 'Group' -Match $PSItem | Select-Object -SkipLast 1 }
$CollectionToDelete | ForEach-Object -Process { Write-Output -InputObject ('Removing: {0}' -f $PSItem.List) }

输出:

Removing: Invitational.Bronze.txt
Removing: Invitational.Silver.txt
Removing: Olympics.Bronze.txt

我的一个烦恼是我无法将我的-matches行转换成[regex]::match行,但这是另一个问题。 [PSCustomObject][System.Management.Automation.PSCustomObject]

不同的事实

最后,我可能会选择@Kirill Pashkov的解决方案,因为它似乎是最优雅的解决方案,并且允许我通过CSV来填充Weight变量(尽管我需要操纵RegEx查询也是如此)。因此,除非我的答案被自动选择为可接受的答案,否则我很可能会选择他的答案。

我在周末损失了一半,但是学到了很多东西,现在可以回去做我的文件服务器操作项目了。谢谢大家的帮助!

编辑:

@Robert Cotterman提出了我花了一整天的超简单方法来完成工作的道具。只是表明它不必复杂。我发现split方法在每个字符的每次出现时都会拆分,但是-split运算符使用RegEx并可以处理整个单词。考虑到这一点,以及奖牌后可能有 的可能性,我最终还是以奖牌代替了replace

[System.String[]]$Collection = 'Invitational.Gold.txt', 'Invitational.Bronze.txt', 'Invitational.Silver.txt', 'Olympics.Silver.txt', 'Olympics.Bronze.txt', 'World.Open.Silver.txt'

$Collection | ForEach-Object -Process {
  If ($PSItem -match 'Silver') {
    $Split = $PSItem -split 'Silver'
    $Name = ('{0}Bronze{1}' -f $Split[0], $Split[1])
    If ($Collection -contains $Name) { "Removing: $Name" }
  } # If
} # ForEach-Object

$Collection | ForEach-Object -Process {
  If ($PSItem -match 'Gold') {
    $Split = $PSItem -split 'Gold'
    $Name = ('{0}Silver{1}' -f $Split[0], $Split[1])
    If ($Collection -contains $Name) { "Removing: $Name" }
  } # If
} # ForEach-Object

输出:

Removing: Invitational.Bronze.txt
Removing: Olympics.Bronze.txt
Removing: Invitational.Silver.txt

@Robert Cotterman的最后编辑帮助我重新考虑了-replace运算符。在所有三枚奖牌都存在的情况下,还需要使用elseif来避免在第二ForEach期间出现重复结果。

[System.String[]]$Collection = 'Invitational.Gold.txt', 'Invitational.Bronze.txt', 'Invitational.Silver.txt', 'Olympics.Silver.txt', 'Olympics.Bronze.txt', 'World.Open.Silver.txt'

$Collection | ForEach-Object -Process {
  If ($PSItem -match 'Silver') {
    $Bronze = $PSItem -replace ('Silver', 'Bronze')
    If ($Collection -contains $Bronze) { "Removing: $Bronze" }
  } # If 'Silver'
} # ForEach-Object

$Collection | ForEach-Object -Process {
  If ($PSItem -match 'Gold') {
    $Silver = $PSItem -replace ('Gold', 'Silver')
    $Bronze = $PSItem -replace ('Gold', 'Bronze')
    If     ($Collection -contains $Silver) { "Removing: $Silver" }
    ElseIf ($Collection -contains $Bronze) { "Removing: $Bronze" }
  } # If 'Gold'
} # ForEach-Object

输出:

Removing: Invitational.Bronze.txt
Removing: Olympics.Bronze.txt
Removing: Invitational.Silver.txt