繁体   English   中英

可以在 PowerShell 中简化以下嵌套 foreach 循环吗?

[英]Can the following Nested foreach loop be simplified in PowerShell?

我创建了一个循环遍历数组并排除在第二个数组中找到的任何变量的脚本。

虽然代码有效; 这让我想知道它是否可以简化或管道化。

   $result = @()
   $ItemArray = @("a","b","c","d")
   $exclusionArray = @("b","c")

    foreach ($Item in $ItemArray)
    {
        $matchFailover = $false
        :gohere
        foreach ($ExclusionItem in $exclusionArray)
        {
            if ($Item -eq $ExclusionItem)
            {
                Write-Host "Match: $Item = $ExclusionItem"
                $matchFailover = $true
                break :gohere
            }
            else{
            Write-Host "No Match: $Item != $ExclusionItem"
            }
        }
        if (!($matchFailover))
        {
            Write-Host "Adding $Item to results"
            $result += $Item
        }
    }
    Write-Host "`nResults are"
    $result

为您的任务命名:您正在寻找两个 arrays 之间的相对补集,即集差异

在集合论符号中,它将是$ItemArray \ $ExclusionArray ,即$ItemArray中的那些元素不在$ExclusionArray中。

这个相关问题正在寻找两个集合之间的对称差异,即任何一方唯一的元素集合 - 最后这就是那里基于Compare-Object的解决方案实现的,但前提是每个数组都没有重复


EyIM 的有用答案概念上简单明了

一个潜在的问题是性能:必须对输入数组中的每个元素执行排除数组中的查找。

对于小型 arrays,这在实践中可能无关紧要。

随着更大的 arrays、 LINQ 提供了一个更快的解决方案

注意为了从 LINQ 解决方案中受益,您的 arrays 应该已经在 memory 中,并且排除数组越大,收益越大。 如果您的输入通过管道流式传输,则执行管道的开销可能会使尝试优化数组处理毫无意义甚至适得其反,在这种情况下,坚持使用本机 PowerShell 解决方案是有意义的 - 请参阅iRon 的回答

# Declare the arrays as [string[]]
# so that calling the LINQ method below works as-is.
# (You could also cast to [string[]] ad hoc.)
[string[]] $ItemArray = 'a','b','c','d'
[string[]] $exclusionArray = 'b','c'

# Return only those elements in $ItemArray that aren't also in $exclusionArray
# and convert the result (a lazy enumerable of type [IEnumerable[string]])
# back to an array to force its evaluation
# (If you directly enumerate the result in a pipeline, that step isn't needed.)
[string[]] [Linq.Enumerable]::Except($ItemArray, $exclusionArray) # -> 'a', 'd'

请注意,需要通过其 static 方法显式使用 LINQ 类型,因为从 v7 开始,PowerShell 不支持扩展方法 但是,在 GitHub 上有一个提案要添加这样的支持; 该相关提案要求改进对调用泛型方法的支持。

有关当前如何从 PowerShell 调用 LINQ 方法的概述,请参阅此答案


性能对比:

iRon 致敬,感谢他的意见。

以下基准代码使用Time-Command function来比较这两种方法,使用分别具有大约 4000 和 2000 个元素的 arrays,它们 - 如问题中一样 - 仅相差 2 个元素。

请注意,为了公平竞争,使用.Where()数组方法(PSv4+) 代替基于管道的Where-Object cmdlet ,因为 memory 中的 arrays 使用.Where()更快。

以下是 10 次运行的平均结果; 注意相对性能,如Factor列中所示; 来自运行 Windows PowerShell v5.1 的单核 Windows 10 VM:

Factor Secs (10-run avg.) Command                              TimeSpan
------ ------------------ -------                              --------
1.00   0.046              # LINQ...                            00:00:00.0455381
8.40   0.382              # Where ... -notContains...          00:00:00.3824038

LINQ 解决方案的速度要快得多 - 提高了 8 倍以上(尽管即使是更慢的解决方案也只需要大约 0.4 秒的运行时间)。

似乎 PowerShell Core的性能差距更大,我在 v7.0.0-preview.4 中看到了大约 19 倍。 有趣的是,这两个测试单独运行都比 Windows PowerShell 运行得更快。

基准代码:

# Script block to initialize the arrays.
# The filler arrays are randomized to eliminate caching effects in LINQ.
$init = {
  $fillerArray = 1..1000 | Get-Random -Count 1000
  [string[]] $ItemArray = $fillerArray + 'a' + $fillerArray + 'b' + $fillerArray + 'c' + $fillerArray + 'd'
  [string[]] $exclusionArray = $fillerArray + 'b' + $fillerArray + 'c'
}

# Compare the average of 10 runs.
Time-Command -Count 10 { # LINQ
  . $init
  $result = [string[]] [Linq.Enumerable]::Except($ItemArray, $exclusionArray)
}, { # Where ... -notContains
  . $init
  $result = $ItemArray.Where({ $exclusionArray -notcontains $_ })
}

您可以将Where-Object-notcontains一起使用:

$ItemArray | Where-Object { $exclusionArray -notcontains $_ }

Output:

a, d

提倡原生PowerShell:
根据@mklement0的回答,毫无疑问, 语言集成查询 (LINQ)// Fast ...
但在某些情况下,使用 @EylM 建议的管道的原生PowerShell命令仍然可以击败 LINQ。 这不仅是理论上的,而且可能发生在相关进程空闲并等待缓慢输入的用例中。 例如输入来自哪里:

  • 远程服务器(例如 Active Directory)
  • 慢速设备
  • 必须进行复杂计算的单独线程
  • 互联网...

尽管我还没有看到一个简单的证明,但这是在几个站点上提出的,并且可以从站点中扣除,例如高性能 PowerShell 与 LINQZ3D265B4E1EEEF0DDF17881FA003B1 管道的来龙去脉

证明

为了证明上述论点,我创建了一个小型Slack cmdlet,它可以在 1 毫秒(默认情况下)减慢放入管道的每个项目:

Function Slack-Object ($Delay = 1) {
    process {
        Start-Sleep -Milliseconds $Delay
        Write-Output $_
    }
}; Set-Alias Slack Slack-Object

现在让我们看看原生 PowerShell 是否真的可以击败 LINQ:
(为了获得良好的性能比较,应通过例如启动新的 PowerShell session 来清除缓存。)

[string[]] $InputArray = 1..200
[string[]] $ExclusionArray = 100..300

(Measure-Command {
    $Result = [Linq.Enumerable]::Except([string[]] ($InputArray | Slack), $ExclusionArray)
}).TotalMilliseconds

(Measure-Command {
    $Result = $InputArray | Slack | Where-Object {$ExclusionArray -notcontains $_}
}).TotalMilliseconds

结果:

      LINQ: 411,3721
PowerShell: 366,961

要排除 LINQ 缓存,应进行单次运行测试,但正如 @mklement0 所评论的,单次运行的结果可能会因每次运行而异。
结果还很大程度上取决于输入 arrays 的大小、结果的大小、松弛度、测试系统等。

结论:

在某些情况下,PowerShell 可能仍然比 LINQ 快!

引用mklement0的评论:
"总的来说,公平地说,在这种情况下,性能差异是如此之小,以至于不值得选择基于性能的方法 - 考虑到 go 使用更像 PowerShell 的方法 ( Where-Object ) 是有意义的, the LINQ approach is far from obvious. The bottom line is: choose LINQ only if you have large arrays that are already in memory. If the pipeline is involved, the pipeline overhead alone may make optimizations pointless. "

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM