Schreiben Sie Powershell-Cmdlets korrekt und simulieren Sie das Monty Hall-Paradoxon

Habr ist definitiv mit dem Paradoxon vertraut, aber wahrscheinlich nicht mit einigen Merkmalen der Pavershell, also hier mehr darüber.









Verwenden der Pipeline in Powershell



Der Algorithmus ist einfach, der erste ist der Zufallstürgenerator, dann der Benutzerauswahlgenerator, dann die Türöffnungslogik des Präsentators, eine weitere Benutzeraktion und das Zählen von Statistiken.



Und das Elektrowerkzeug hilft uns dabei ValueFromPipeline



, das Cmdlet einzeln anzugeben und das Objekt Schritt für Schritt zu transformieren. So sollte unsere Pipeline aussehen:



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





New-Doors



erzeugt neue Türen, im Team Select-Door



wählt der Spieler eine der Türen, der Open-Door



Anführer öffnet die Tür, in der es definitiv keine Ziege gibt und die vom Spieler nicht ausgewählt wurde, und in Invoke-UserAction



simulieren wir ein anderes Benutzerverhalten.



Das Objekt, das die Tür beschreibt, bewegt sich von links nach rechts und verwandelt sich allmählich.



Diese Methode zum Schreiben von Code hilft, ihn in Teilen mit klaren Verantwortungsbereichen zu halten.



Powershell hat seine eigenen Konventionen. Einschließlich der Konventionen zur korrekten Benennung von Funktionen müssen diese ebenfalls beachtet werden und wir halten sie fast ein.



Türen machen



Da wir die Situation simulieren werden, werden wir auch die Türen im Detail beschreiben.



Die Tür enthält entweder eine Ziege oder ein Auto. Die Tür kann vom Spieler ausgewählt oder vom Gastgeber geöffnet werden.



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

      
      





Wir werden jede der Türen in einem separaten Feld in einer separaten Klasse platzieren.



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





Es war möglich, alle Türen in einer Reihe anzuordnen, aber je detaillierter alles beschrieben wird, desto besser. Übrigens sind in Powershell 7 Klassen, ihre Konstruktoren, Methoden und alles andere OOP, was fast so funktioniert, wie es sollte, aber dazu ein anderes Mal mehr. 



Der Zufallstürgenerator sieht so aus. Zuerst wird für jeden Türpfosten eine eigene Tür erzeugt, und dann wählt der Generator aus, hinter welcher von ihnen das Auto stehen soll.



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

      
      





Unsere Pfeife sieht so aus:



New-Doors
      
      





Der Spieler wählt die Tür



Beschreiben wir nun die anfängliche Wahl. Der Spieler kann eine von drei Türen wählen. Um mehr Situationen zu simulieren, lassen Sie den Spieler jedes Mal nur die erste, nur die zweite, nur die dritte und zufällige Tür auswählen. 



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





Um Argumente aus der Pipeline zu akzeptieren, müssen Sie im Parameterblock eine Variable angeben, die dies ausführt. Dies geschieht folgendermaßen:



[parameter(ValueFromPipeline)]
[Doors]$i
      
      





Sie können ValueFromPipeline



ohne schreiben True



.



So sieht der fertige Türauswahlblock aus:



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 

      
      





Unsere Pfeife sieht so aus:



New-Doors | Select-Door -Principle Random
      
      





Führen öffnet die Tür



Hier ist alles sehr einfach. Wenn die Tür nicht vom Spieler ausgewählt wurde und sich eine Ziege dahinter befindet, ändern Sie das Feld Opened



in True



. Insbesondere ist es in diesem Fall Open



nicht korrekt , den Befehl als Wort zu bezeichnen . Die aufgerufene Ressource wird nicht gelesen, sondern geändert. In solchen Fällen verwenden Set



, aber Open



zur Klarheit lassen.



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

      
      





Um unsere Simulation überzeugender zu gestalten, "öffnen" wir diese Tür, indem wir das geöffnete Feld in ändern, anstatt das $true



Objekt aus dem Türarray zu löschen .



Vergessen Sie nicht die continue



Schalter, der Vergleich wird nach dem ersten Spiel nicht beendet. Coninue



Beendet den Switch und führt das Skript weiter aus. Der Operator break



im Switch beendet das Skript.



Wir fügen der Pipe eine weitere Funktion hinzu, die jetzt so aussieht:



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





Der Spieler ändert seine Wahl 



Der Spieler wechselt entweder die Tür oder ändert sie nicht. Im Parameterblock haben wir nur eine Variable aus der Pipe und ein boolesches Argument. 



Verwenden Sie das Wort Invoke



in den Namen solcher Funktionen, da dies Invoke



bedeutet, dass eine synchrone Operation Start



aufgerufen und asynchron die Konventionen und Empfehlungen befolgt werden.



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

      
      





In den Operatoren Verzweigung und Vergleich müssen Sie zuerst das System und die statischen Variablen angeben. Wahrscheinlich kann es Schwierigkeiten geben, ein Objekt in ein anderes umzuwandeln, aber der Autor hatte solche Schwierigkeiten nicht, als er zuvor auf eine andere Weise schrieb.



Eine weitere Funktion in der Pipeline.



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





Der Vorteil dieses Schreibansatzes liegt auf der Hand, da es noch nie so bequem war, Code mit einer klaren Funktionstrennung in Teile aufzuteilen.



Spielerverhalten



Wie oft wechselt der Spieler die Tür. Es gibt 5 Verhaltenslinien:



  1. Never



    - Der Spieler ändert nie seine Wahl
  2. Fifty-Fifty



    - 50 bis 50. Die Anzahl der Simulationen ist in zwei Durchgänge unterteilt. Beim ersten Durchgang ändert der Spieler die Tür nicht, beim zweiten Durchgang ändert sich.
  3. Random



    - In jeder neuen Simulation wirft der Spieler eine Münze
  4. Always



    - Der Spieler ändert immer seine Wahl.
  5. Ration



    - Der Spieler ändert seine Wahl in N% der Fälle.


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



In Powershell 7 funktioniert es viel schneller als eine Schleife for



. Außerdem kann es parallelisiert werden, sodass es hier anstelle einer Schleife verwendet wird for



.



Cmdlet stylen



Jetzt müssen Sie das Cmdlet korrigieren. Zunächst müssen Sie die eingehenden Argumente validieren. Der Bonus besteht nicht nur darin, dass eine Person kein ungültiges Argument in das Feld eingeben kann, sondern eine Liste aller verfügbaren Argumente in den Eingabeaufforderungen angezeigt wird.



So sieht der Code im Parameterblock aus:



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

      
      





Dies ist der Hinweis:





Bevor der Parameterblock ausgeführt werden kann comment based help



. So sieht der Code vor dem Parameterblock aus:




  <#
      .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
   
      #>

      
      





So sieht die Eingabeaufforderung aus:





Ausführen der Simulation



Simulationsergebnisse:





Wenn eine Person ihre Wahl nie ändert, gewinnt sie 33,37% der Zeit.



Bei zwei Pässen, bei denen wir uns zur Hälfte weigern, unsere Wahl zu ändern, liegen die Gewinnchancen bei 49,9134%, was sehr genau 50% entspricht.



Bei einem Münzwurf ändert sich nichts, die Gewinnchance bleibt bei 50,131%.



Wenn der Spieler seine Wahl immer ändert, steigt die Gewinnchance auf 66,6184%, dh langweilig und nichts Neues.



Leistung:



In Bezug auf die Leistung. Das Skript scheint nicht optimal zu sein. String



Stattdessen Bool



gibt es viele verschiedene Funktionen mit einem Schalter im Inneren, der ein Objekt aneinander weitergibt. Dennoch sind hier die Ergebnisse Measure-Command



für dieses Skript und ein Skript eines anderen Autors .



Der Vergleich wurde auf zwei Systemen durchgeführt, pwsh 7.1 war überall, 100.000 Durchgänge.



▍I5-5200u



Dieser Algorithmus:



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
      
      





Dieser Algorithmus:



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



Dieser Algorithmus:



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
      
      





Dieser Algorithmus:



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
      
      





63ms Vorteil, aber die Ergebnisse sind immer noch sehr seltsam, wenn man bedenkt, wie oft das Skript Zeichenfolgen vergleicht.



Der Autor hofft, dass dieser Artikel als überzeugendes Beispiel für diejenigen dienen wird, die glauben, dass die Chancen immer zwischen 50 und 50 liegen, aber Sie können den Code unter diesem Spoiler lesen.



Der ganze Code
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