Menulis cmdlet Powershell dengan benar dan menyimulasikan paradoks Monty Hall

Habr pasti akrab dengan paradoks, tapi mungkin tidak dengan beberapa fitur pavershell, jadi berikut ini lebih banyak tentangnya.









Menggunakan pipa di Powershell



Algoritmanya sederhana, yang pertama adalah generator pintu acak, lalu generator pilihan pengguna, lalu logika pembukaan pintu presenter, tindakan pengguna lain, dan penghitungan statistik.



Dan perkakas listrik akan membantu kita dalam hal ini ValueFromPipeline



, yang memungkinkan kita menentukan cmdlet satu per satu, mengubah objek selangkah demi selangkah. Pipa kami akan terlihat seperti ini:



New-Doors | Select-Door | Open-Door | Invoke-UserAction
      
      





New-Doors



menghasilkan pintu baru, dalam tim Select-Door



pemain memilih salah satu pintu, Open-Door



pemimpin membuka pintu di mana pasti tidak ada kambing dan yang tidak dipilih oleh pemain, dan Invoke-UserAction



kami mensimulasikan perilaku pengguna yang berbeda.



Objek yang mendeskripsikan pintu bergerak dari kiri ke kanan, bertransformasi secara bertahap.



Metode penulisan kode ini membantu menjaganya tetap utuh dengan pembagian tanggung jawab yang jelas.



Powershell memiliki konvensi sendiri. Termasuk konvensi tentang penamaan fungsi yang benar , mereka juga perlu diperhatikan dan kita hampir mematuhinya.



Membuat pintu



Karena kami akan mensimulasikan situasinya, kami juga akan menjelaskan pintu secara rinci.



Pintunya berisi seekor kambing atau mobil. Pintu bisa dipilih oleh pemain atau dibuka oleh tuan rumah.



class Door {
    <#
     ,    . 
            .
    #>
    [string]$Contains = "Goat"
    [bool]$Selected = $false
    [bool]$Opened = $false
}

      
      





Kami akan menempatkan setiap pintu di bidang terpisah di kelas terpisah.



class Doors {
    <#
     ,   3 
    #>
    [Door]$DoorOne 
    [Door]$DoorTwo 
    [Door]$DoorThree
}
      
      





Dimungkinkan untuk menempatkan mereka semua pintu dalam satu larik, tetapi semakin rinci semuanya dijelaskan, semakin baik. Ngomong-ngomong, di Powershell 7, kelas, konstruktor, metode, dan lainnya adalah OOP, yang berfungsi hampir sebagaimana mestinya, tetapi lebih dari itu di lain waktu. 



Generator pintu acak terlihat seperti ini. Pertama, untuk setiap kusen pintu, pintunya sendiri dibuat, dan kemudian generator memilih mobil mana yang akan berdiri di belakang.



function New-Doors {
    <#
      .
    #>
    $i = [Doors]::new()
 
    $i.DoorOne = [Door]::new()
    $i.DoorTwo = [Door]::new()
    $i.DoorThree = [Door]::new()
 
    switch ( Get-Random -Maximum 3 -Minimum 0 ) {
        0 { 
            $i.DoorOne.Contains = "Car"
        }
        1 { 
            $i.DoorTwo.Contains = "Car"
        }
        2 { 
            $i.DoorThree.Contains = "Car"
        }
        Default {
            Write-Error "Something in door generator went wrong"
            break
        }
    }
    
    return $i

      
      





Pipa kami terlihat seperti ini:



New-Doors
      
      





Pemain memilih pintu



Sekarang mari kita gambarkan pilihan awal. Pemain dapat memilih salah satu dari tiga pintu. Untuk tujuan simulasi lebih banyak situasi, biarkan pemain memilih hanya yang pertama, hanya yang kedua, hanya yang ketiga, dan pintu acak setiap saat. 



[Parameter(Mandatory)]
[ValidateSet("First", "Second", "Third", "Random")]
$Principle
      
      





Untuk menerima argumen dari pipeline, Anda perlu menentukan variabel di blok parameter yang akan melakukan ini. Ini dilakukan seperti ini:



[parameter(ValueFromPipeline)]
[Doors]$i
      
      





Anda bisa menulis ValueFromPipeline



tanpa True



.



Seperti inilah tampilan blok pemilihan pintu:



function Select-Door {
    <#
      .
    #>
    Param (
        [parameter(ValueFromPipeline)]
        [Doors]$i,
        [Parameter(Mandatory)]
        [ValidateSet("First", "Second", "Third", "Random")]
        $Principle
    )
    
    switch ($Principle) {
        "First" {
            $i.DoorOne.Selected = $true
        }
        "Second" {
            $i.DoorTwo.Selected = $true
        }
        "Third" {
            $i.DoorThree.Selected = $true
        }
        "Random" {
            switch ( Get-Random -Maximum 3 -Minimum 0 ) {
                0 { 
                    $i.DoorOne.Selected = $true
                }
                1 { 
                    $i.DoorTwo.Selected = $true
                }
                2 { 
                    $i.DoorThree.Selected = $true
                }
                Default {
                    Write-Error "Something in door selector went wrong"
                    break
                }
            }
        }
        Default {
            Write-Error "Something in door selector went wrong"
            break
        }
    }
 
    return $i 

      
      





Pipa kami terlihat seperti ini:



New-Doors | Select-Door -Principle Random
      
      





Memimpin membuka pintu



Semuanya sangat sederhana di sini. Jika pintu tidak dipilih oleh pemain dan jika ada kambing di belakangnya, maka ganti bidang Opened



ke True



. Secara khusus, dalam kasus ini, Open



tidak benar untuk memanggil perintah sebagai kata , sumber daya yang dipanggil tidak dibaca, tetapi diubah. Dalam kasus seperti itu, gunakan Set



, tetapi Open



biarkan untuk kejelasan.



function Open-Door {
    <#
        ,   ,   .
    #>
    Param (
        [parameter(ValueFromPipeline)]
        [Doors]$i
    )
    switch ($false) {
        $i.DoorOne.Selected {
            if ($i.DoorOne.Contains -eq "Goat") {
                $i.DoorOne.Opened = $true
                continue
            }
           
        }
        $i.DoorTwo.Selected { 
            if ($i.DoorTwo.Contains -eq "Goat") {
                $i.DoorTwo.Opened = $true
                continue
            }
           
        }
        $i.DoorThree.Selected { 
            if ($i.DoorThree.Contains -eq "Goat") {
                $i.DoorThree.Opened = $true
                continue
            }
            
        }
    }
    return $i

      
      





Untuk membuat simulasi kita lebih meyakinkan, kita "membuka" pintu ini dengan mengubah bidang .opened menjadi $true



alih-alih memindahkan objek dari deretan pintu.



Jangan lupa tentang continue



sakelar, perbandingan tidak berhenti setelah pertandingan pertama. Coninue



keluar dari sakelar dan terus mengeksekusi skrip, dan operator break



di sakelar mengakhiri skrip.



Tambahkan satu fungsi lagi ke pipa, sekarang terlihat seperti ini:



New-Doors | Select-Door -Principle Random | Open-Door
      
      





Pemain mengubah pilihannya 



Pemain mengganti pintu atau tidak. Di blok parameter, kami hanya memiliki variabel dari pipa dan argumen boolean. 



Gunakan kata Invoke



dalam nama fungsi tersebut, karena itu Invoke



berarti memanggil operasi sinkron, dan Start



asinkron, ikuti konvensi dan rekomendasi.



function Invoke-UserAction {
    <#
    ,        .
    #>
    Param (
        [parameter(ValueFromPipeline)]
        [Doors]$i,
        [Parameter(Mandatory)]
        [bool]$SwitchDoor
    )
 
    if ($true -eq $SwitchDoor) {
        switch ($false) {
            $i.DoorOne.Opened {  
                if ( $i.DoorOne.Selected ) {
                    $i.DoorOne.Selected = $false
                }
                else {
                    $i.DoorOne.Selected = $true
                }
            }
            $i.DoorTwo.Opened {
                if ( $i.DoorTwo.Selected ) {
                    $i.DoorTwo.Selected = $false
                }
                else {
                    $i.DoorTwo.Selected = $true
                }
            }
            $i.DoorThree.Opened {
                if ( $i.DoorThree.Selected ) {
                    $i.DoorThree.Selected = $false
                }
                else {
                    $i.DoorThree.Selected = $true
                }
            }
        }  
    }
 
    return $i

      
      





Dalam operator percabangan dan perbandingan, variabel sistem dan statis harus ditentukan terlebih dahulu. Mungkin, mungkin ada kesulitan dalam mengonversi satu objek ke objek lain, tetapi penulis tidak menemui kesulitan seperti itu ketika dia menulis dengan cara yang berbeda sebelumnya.



Fungsi lain di dalam pipa.



New-Doors | Select-Door -Principle Random | Open-Door | Invoke-UserAction -SwitchDoor $True
      
      





Keuntungan dari pendekatan penulisan ini jelas, karena tidak pernah semudah ini untuk membagi kode menjadi beberapa bagian dengan pemisahan fungsi yang jelas.



Perilaku pemain



Seberapa sering pemain mengganti pintu. Ada 5 baris perilaku:



  1. Never



    - pemain tidak pernah mengubah pilihannya
  2. Fifty-Fifty



    - 50 hingga 50. Jumlah simulasi dibagi menjadi dua lintasan. Pass pertama pemain tidak mengubah pintu, operan kedua berubah.
  3. Random



    - di setiap simulasi baru, pemain membalik koin
  4. Always



    - pemain selalu mengubah pilihannya.
  5. Ration



    - pemain mengubah pilihannya dalam N% kasus.


switch ($SwitchDoors) {
        "Never" { 
            0..$Count | ForEach-Object {
                $Win += Invoke-Simulation -Door $Door -SwitchDoors $false
            }
            continue
        }
        "FiftyFifty" {
            $Fifty = [math]::Round($Count / 2)
 
            0..$Fifty | ForEach-Object {
                $Win += Invoke-Simulation -Door $Door -SwitchDoors $false
            }
 
            0..$Fifty | ForEach-Object {
                $Win += Invoke-Simulation -Door $Door -SwitchDoors $true
            }
            continue
        }
        "Random" {
            0..$Count | ForEach-Object {
                [bool]$Random = Get-Random -Maximum 2 -Minimum 0
                $Win += Invoke-Simulation -Door $Door -SwitchDoors $Random
            }
            continue
        }
        "Always" {
            0..$Count | ForEach-Object {
                $Win += Invoke-Simulation -Door $Door -SwitchDoors $true
            }
            continue
        }
        "Ratio" {
            $TrueRatio = $Ratio / 100 * $Count 
            $FalseRatio = $Count - $TrueRatio
 
            0..$TrueRatio | ForEach-Object {
                $Win += Invoke-Simulation -Door $Door -SwitchDoors $true
            }
 
            0..$FalseRatio | ForEach-Object {
                $Win += Invoke-Simulation -Door $Door -SwitchDoors $false
            }
            continue
        }
    }

      
      





ForEach-Object



di Powershell 7 ini bekerja jauh lebih cepat daripada satu loop for



, ditambah itu bisa diparalelkan, jadi ini digunakan di sini sebagai ganti loop for



.



Menata cmdlet



Sekarang Anda perlu mengoreksi cmdlet. Pertama-tama, Anda perlu melakukan validasi argumen yang masuk. Bonusnya tidak hanya seseorang tidak bisa memasukkan argumen yang tidak valid di lapangan, tetapi daftar semua argumen yang tersedia muncul di prompt.



Seperti inilah tampilan kode di blok parameter:



param (
        [Parameter(Mandatory = $false,
            HelpMessage = "How often the player changes his choice.")]
        [ValidateSet("Never", "FiftyFifty", "Random", "Always", "Ratio")]
        $SwitchDoors = "Random"
    )

      
      





Ini petunjuknya:





Sebelum blok parameter bisa dilakukan comment based help



. Ini adalah kode yang terlihat sebelum blok parameter:




  <#
      .SYNOPSIS
   
      Performs monty hall paradox simulation.
   
      .DESCRIPTION
   
      The Invoke-MontyHallParadox.ps1 script invoke monty hall paradox simulation.
   
      .PARAMETER Door
      Specifies door the player will choose during the entire simulation
   
      .PARAMETER SwitchDoors
      Specifies principle how the player changes his choice.
   
      .PARAMETER Count
      Specifies how many times to run the simulation.
   
      .PARAMETER Ratio
      If -SwitchDoors Ratio, specifies how often the player changes his choice. As a percentage."
   
      .INPUTS
   
      None. You cannot pipe objects to Update-Month.ps1.
   
      .OUTPUTS
   
      None. Update-Month.ps1 does not generate any output.
   
      .EXAMPLE
   
      PS> Invoke-MontyHallParadox -SwitchDoors Always -Count 10000
   
      #>

      
      





Seperti inilah tampilan promptnya:





Menjalankan simulasi



Hasil simulasi:





Jika seseorang tidak pernah mengubah pilihannya, maka dia menang 33,37% dari waktu.



Dalam kasus dua operan, di mana setengahnya kami menolak untuk mengubah pilihan kami, peluang menang adalah 49,9134%, yang sangat dekat dengan tepat 50%.



Dalam kasus lemparan koin, tidak ada yang berubah, peluang menang tetap sekitar 50,131%.



Nah, jika pemain selalu mengubah pilihannya maka peluang menang meningkat menjadi 66.6184%, dengan kata lain membosankan dan bukan hal baru.



Kinerja:



Dalam hal kinerja. Skripnya sepertinya tidak optimal. String



alih-alih Bool



, banyak fungsi berbeda dengan sakelar di dalamnya, meneruskan objek satu sama lain, namun demikian, berikut adalah hasilnya Measure-Command



untuk skrip ini dan skrip dari penulis lain .



Perbandingan dilakukan pada dua sistem, pwsh 7.1 ada dimana-mana, 100.000 lintasan.



▍I5-5200u



Algoritma ini:



Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 4
Milliseconds      : 581
Ticks             : 45811819
TotalDays         : 5,30229386574074E-05
TotalHours        : 0,00127255052777778
TotalMinutes      : 0,0763530316666667
TotalSeconds      : 4,5811819
TotalMilliseconds : 4581,1819
      
      





Algoritme itu:



Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 5
Milliseconds      : 104
Ticks             : 51048392
TotalDays         : 5,9083787037037E-05
TotalHours        : 0,00141801088888889
TotalMinutes      : 0,0850806533333333
TotalSeconds      : 5,1048392
TotalMilliseconds : 5104,8392
      
      





▍I9-9900K



Algoritma ini:



Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 1
Milliseconds      : 891
Ticks             : 18917629
TotalDays         : 2,18954039351852E-05
TotalHours        : 0,000525489694444444
TotalMinutes      : 0,0315293816666667  
TotalSeconds      : 1,8917629
TotalMilliseconds : 1891,7629
      
      





Algoritme itu:



Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 1
Milliseconds      : 954
Ticks             : 19543236
TotalDays         : 2,26194861111111E-05
TotalHours        : 0,000542867666666667
TotalMinutes      : 0,03257206
TotalSeconds      : 1,9543236
TotalMilliseconds : 1954,3236
      
      





Keuntungan 63ms, tetapi hasilnya masih sangat aneh mengingat berapa kali skrip membandingkan string.



Penulis berharap artikel ini dapat menjadi contoh yang meyakinkan bagi mereka yang percaya bahwa peluangnya selalu 50 hingga 50, tetapi Anda dapat membaca kode di bawah spoiler ini.



Seluruh kode
class Doors {

<#

, 3

#>

[Door]$DoorOne

[Door]$DoorTwo

[Door]$DoorThree

}



class Door {

<#

, .

.

#>

[string]$Contains = «Goat»

[bool]$Selected = $false

[bool]$Opened = $false

}



function New-Doors {

<#

.

#>

$i = [Doors]::new()



$i.DoorOne = [Door]::new()

$i.DoorTwo = [Door]::new()

$i.DoorThree = [Door]::new()



switch ( Get-Random -Maximum 3 -Minimum 0 ) {

0 {

$i.DoorOne.Contains = «Car»

}

1 {

$i.DoorTwo.Contains = «Car»

}

2 {

$i.DoorThree.Contains = «Car»

}

Default {

Write-Error «Something in door generator went wrong»

break

}

}



return $i

}



function Select-Door {

<#

.

#>

Param (

[parameter(ValueFromPipeline)]

[Doors]$i,

[Parameter(Mandatory)]

[ValidateSet(«First», «Second», «Third», «Random»)]

$Principle

)



switch ($Principle) {

«First» {

$i.DoorOne.Selected = $true

continue

}

«Second» {

$i.DoorTwo.Selected = $true

continue

}

«Third» {

$i.DoorThree.Selected = $true

continue

}

«Random» {

switch ( Get-Random -Maximum 3 -Minimum 0 ) {

0 {

$i.DoorOne.Selected = $true

continue

}

1 {

$i.DoorTwo.Selected = $true

continue

}

2 {

$i.DoorThree.Selected = $true

continue

}

Default {

Write-Error «Something in selector generator went wrong»

break

}

}

continue

}

Default {

Write-Error «Something in door selector went wrong»

break

}

}



return $i

}



function Open-Door {

<#

, , .

#>

Param (

[parameter(ValueFromPipeline)]

[Doors]$i

)

switch ($false) {

$i.DoorOne.Selected {

if ($i.DoorOne.Contains -eq «Goat») {

$i.DoorOne.Opened = $true

continue

}

}

$i.DoorTwo.Selected {

if ($i.DoorTwo.Contains -eq «Goat») {

$i.DoorTwo.Opened = $true

continue

}

}

$i.DoorThree.Selected {

if ($i.DoorThree.Contains -eq «Goat») {

$i.DoorThree.Opened = $true

continue

}

}

}

return $i

}



function Invoke-UserAction {

<#

, .

#>

Param (

[parameter(ValueFromPipeline)]

[Doors]$i,

[Parameter(Mandatory)]

[bool]$SwitchDoor

)



if ($true -eq $SwitchDoor) {

switch ($false) {

$i.DoorOne.Opened {

if ( $i.DoorOne.Selected ) {

$i.DoorOne.Selected = $false

}

else {

$i.DoorOne.Selected = $true

}

}

$i.DoorTwo.Opened {

if ( $i.DoorTwo.Selected ) {

$i.DoorTwo.Selected = $false

}

else {

$i.DoorTwo.Selected = $true

}

}

$i.DoorThree.Opened {

if ( $i.DoorThree.Selected ) {

$i.DoorThree.Selected = $false

}

else {

$i.DoorThree.Selected = $true

}

}

}

}



return $i

}



function Get-Win {

Param (

[parameter(ValueFromPipeline)]

[Doors]$i

)

switch ($true) {

($i.DoorOne.Selected -and $i.DoorOne.Contains -eq «Car») {

return $true

}

($i.DoorTwo.Selected -and $i.DoorTwo.Contains -eq «Car») {

return $true

}

($i.DoorThree.Selected -and $i.DoorThree.Contains -eq «Car») {

return $true

}

default {

return $false

}

}

}



function Invoke-Simulation {

param (

[Parameter(Mandatory = $false,

HelpMessage = «Which door the player will choose during the entire simulation.»)]

[ValidateSet(«First», «Second», «Third», «Random»)]

$Door = «Random»,



[bool]$SwitchDoors

)

return New-Doors | Select-Door -Principle $Door | Open-Door | Invoke-UserAction -SwitchDoor $SwitchDoors | Get-Win

}



function Invoke-MontyHallParadox {

<#

.SYNOPSIS



Performs monty hall paradox simulation.



.DESCRIPTION



The Invoke-MontyHallParadox.ps1 script invoke monty hall paradox simulation.



.PARAMETER Door

Specifies door the player will choose during the entire simulation



.PARAMETER SwitchDoors

Specifies principle how the player changes his choice.



.PARAMETER Count

Specifies how many times to run the simulation.



.PARAMETER Ratio

If -SwitchDoors Ratio, specifies how often the player changes his choice. As a percentage."



.INPUTS



None. You cannot pipe objects to Update-Month.ps1.



.OUTPUTS



None. Update-Month.ps1 does not generate any output.



.EXAMPLE



PS> Invoke-MontyHallParadox -SwitchDoors Always -Count 10000



#>

param (

[Parameter(Mandatory = $false,

HelpMessage = «Which door the player will choose during the entire simulation.»)]

[ValidateSet(«First», «Second», «Third», «Random»)]

$Door = «Random»,



[Parameter(Mandatory = $false,

HelpMessage = «How often the player changes his choice.»)]

[ValidateSet(«Never», «FiftyFifty», «Random», «Always», «Ratio»)]

$SwitchDoors = «Random»,



[Parameter(Mandatory = $false,

HelpMessage = «How many times to run the simulation.»)]

[uint32]$Count = 10000,



[Parameter(Mandatory = $false,

HelpMessage = «How often the player changes his choice. As a percentage.»)]

[uint32]$Ratio = 30

)



[uint32]$Win = 0



switch ($SwitchDoors) {

«Never» {

0..$Count | ForEach-Object {

$Win += Invoke-Simulation -Door $Door -SwitchDoors $false

}

continue

}

«FiftyFifty» {

$Fifty = [math]::Round($Count / 2)



0..$Fifty | ForEach-Object {

$Win += Invoke-Simulation -Door $Door -SwitchDoors $false

}



0..$Fifty | ForEach-Object {

$Win += Invoke-Simulation -Door $Door -SwitchDoors $true

}

continue

}

«Random» {

0..$Count | ForEach-Object {

[bool]$Random = Get-Random -Maximum 2 -Minimum 0

$Win += Invoke-Simulation -Door $Door -SwitchDoors $Random

}

continue

}

«Always» {

0..$Count | ForEach-Object {

$Win += Invoke-Simulation -Door $Door -SwitchDoors $true

}

continue

}

«Ratio» {

$TrueRatio = $Ratio / 100 * $Count

$FalseRatio = $Count — $TrueRatio



0..$TrueRatio | ForEach-Object {

$Win += Invoke-Simulation -Door $Door -SwitchDoors $true

}



0..$FalseRatio | ForEach-Object {

$Win += Invoke-Simulation -Door $Door -SwitchDoors $false

}

continue

}

}



Write-Output («Player won in » + $Win + " times out of " + $Count)

Write-Output («Whitch is » + ($Win / $Count * 100) + "%")



return $Win

}



#Invoke-MontyHallParadox -SwitchDoors Always -Count 500000












All Articles