簡體   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