StartSkripte ∷ PowerShell

Die nachfolgenden Skripte können kopiert und in der Microsoft PowerShell ISE oder sogar online ausgeführt werden. Seit Version 6 ist PowerShell unter Open-Source-Lizenz für diverse Betriebssysteme erhältlich.
 

Wochentag der Geburt und Anzahl der seitdem vergangenen Tage

Die Funktion BirthCalc im nachfolgenden Skript nimmt ein Geburtsdatum im Format JJJJ-MM-TT entgegen und ermittelt sowohl den Wochentag der Geburt als auch die seitdem vergangenen Tage:

function BirthCalc([String]$s) {
  $f = "Du bist vor {0} Tagen an einem {1} geboren worden!"
  $e = "Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag"
  if ($s -match '^\d{4}(-\d\d){2}$' -and ($d = $s -as [DateTime])) {
    return $f -f ((Get-Date) - $d).Days, (-split $e)[$d.DayOfWeek]
  }
}
BirthCalc 2002-02-20
# Du bist vor 6666 Tagen an einem Mittwoch geboren worden!

Um die Berechnungen für ein anderes Datum vorzunehmen, muss nur der obige Funktionsaufruf vor seiner Ausführung entsprechend angepasst werden. Eine Schnaps- oder runde Zahl hinsichtlich der Anzahl der Lebenstage kann einen guten Vorwand liefern, um einfach mal jemanden überraschend zu kontaktieren und mit Verweis auf das Ereignis zu gratulieren.

Konvertierung römischer Zahlen

Die Funktion RomanToInt nimmt eine Zeichenkette mit den römischen Ziffern I, V, X, L, C, D, M entgegen und konvertiert sie in eine natürliche Zahl. Im Gegenzug wandelt die Funktion IntToRoman eine natürliche Zahl in eine römische um. Der Eingabebereich beider Funktionen liegt zwischen 1 und 3999 (bzw. I und MMMCMXCIX).

Die Funktion ConvertTo-Roman nimmt eine Menge von natürlichen und/oder römischen Zahlen entgegen und stellt sie tabellarisch in beiden Formaten dar, wobei implizit auch eine Normalisierung der Schreibweise vorgenommen wird (siehe nachfolgende Erläuterungen zur Variablen $r). Für notwendige Umrechnungen wird auf die Funktionen RomanToInt und IntToRoman zurückgegriffen.

function RomanToInt([String]$s) {
  $n = 0
  $r = '(M{1,3})?' + # Or M{1,9} for numbers up to 9999
       '(?:((?:CC?|XX?|II?)[MD])|((?:DC?|C)C{0,3}))?' +
           '(?:((?:XX?|II?)[CL])|((?:LX?|X)X{0,3}))?' +
               '(?:((?:II?)[XV])|((?:VI?|I)I{0,3}))?'
  if ($s -and $s -cmatch "^$r`$") {
    $h = @{ I=1; V=5; X=10; L=50; C=100; D=500; M=1000 }
    foreach ($k in $Matches.Keys -gt 0) {
      $a = $h[[String]$Matches[$k][-1]]
      $b = $Matches[$k].Length - 1
      $c = $h[[String]$Matches[$k][0]]
      $n += &({ $a - $b * $c }, { $a * $b + $c })[$k % 2]
    }
  }
  return $n
}
function IntToRoman([ValidateRange(0,3999)][Int]$n) {
  $s, $a = "", "I", "V", "X", "L", "C", "D", "M"
  for ($i = 0; $n; ($n = [Math]::Floor($n / 10)), ($i += 2)) {
    # The following line is only required for numbers > 3999
    # if ($a.Count - $i -lt 3) { return $a[$i] * $n + $s }
    switch ($n % 10) {
      { 1..3 -eq $_ % 5 } { $s = $a[$i] * ($_ % 5) + $s }
      { 4    -le $_ }     { $s = $a[$i + 1 + ($_ -eq 9)] + $s }
      { 4, 9 -eq $_ }     { $s = $a[$i] + $s }
    }
  }
  return $s
}
function ConvertTo-Roman {
  param([Parameter(ValueFromPipeline=$true)]$InputObject)
  Begin {
    $o = 1 | Select-Object -Property Arabic, Roman
    $a = { $e -as [Int] }, { RomanToInt $e }
  }
  Process {
    foreach ($e in $InputObject) {
      $o.Arabic = &$a[$e -is [String]]
      $o.Roman = IntToRoman $o.Arabic
      Write-Output -InputObject $o
    }
  }
}
RomanToInt MXXM
# 1980
IntToRoman 1980
# MCMLXXX
ConvertTo-Roman MDCCCCLXXXIIII, MCMLXXXIV, $true, 1666
# Arabic Roman    
# ------ -----    
#   1984 MCMLXXXIV
#   1984 MCMLXXXIV
#      1 I        
#   1666 MDCLXVI  
1..3999 | ConvertTo-Roman | Format-Table -Autosize

In der Funktion RomanToInt wird der Variablen $r ein regulärer Ausdruck zugewiesen, durch den festgelegt wird, wie eine römische Zahl aussehen muss. Dieser ist im obigen Skript derart gestaltet, dass auch einige alternative Schreibweisen wie MXXM für 1980 (also Verdoppelung des Zeichens in subtraktiver Stellung) oder VIIII für 9 (d. h. keine Anwendung einer Subtraktionsregel) akzeptiert werden. Soll dies nicht erlaubt sein, so muss das Muster restriktiver formuliert werden, beispielsweise wie folgt:

  $r = '(M{1,3})?' +
       '(?:(CM|CD)|(DC{0,3}|C{1,3}))?' +
       '(?:(XC|XL)|(LX{0,3}|X{1,3}))?' +
       '(?:(IX|IV)|(VI{0,3}|I{1,3}))?'

Mittels des regulären Ausdrucks wird nicht nur eine Zeichenkette aus römischen Zahlzeichen auf Gültigkeit überprüft, sondern auch in Einer-, Zehner, Hunderter- und Tausenderstellen zerlegt. Dabei gibt es für jede Wertigkeit (bis auf die höchste) zwei Gruppierungen, wobei die jeweils erste subtraktive Schreibungen erkennt (z. B. IV oder IIX) und die andere alle additiven/übrigen (z. B. VI oder IIII, aber auch V).

Türme von Hanoi

Die nachfolgende rekursive Funktion schlägt eine Zugfolge zur Lösung der Türme von Hanoi vor. Als Parameter müssen die Anzahl der Scheiben sowie die Namen der 3 Ablegepositionen (z. B. L für Links, M für Mitte und R für Rechts) angegeben werden. Auf der ersten Ablegeposition (hier: L) ruht der Turm zu Beginn; Ziel ist es, ihn unter Einhaltung der Regeln auf die letzte Ablegeposition (hier: R) zu versetzen. Die Einrückungen in den ausgegebenen Zeilen entsprechen jeweils der Breite der gerade bewegten Scheibe.

function Tower-of-Hanoi {
  param([Int]$num, [String]$src, [String]$tmp, [String]$dst)
  if ($num-- -gt 0) {
    Tower-of-Hanoi $num $src $dst $tmp
    Write-Host -Object (" " * $num + $src + "->" + $dst)
    Tower-of-Hanoi $num $tmp $src $dst
  }
}
Tower-of-Hanoi 3 L M R
# L->R
#  L->M
# R->M
#   L->R
# M->L
#  M->R
# L->R

Der obige Algorithmus löst die Aufgabe für n Scheiben, indem er

  1. n-1 Scheiben von der linken zur mittleren Ablegeposition versetzt und dabei die rechte Position temporär benutzt,
  2. dann die breiteste Scheibe von der linken zur rechten Ablegeposition zieht und
  3. abschließend die n-1 Scheiben von der mittleren zur rechten Ablegeposition versetzt, wobei die linke Position temporär benutzt wird.

Durch dieses rekursive Vorgehen ist die Aufgabe für n Scheiben lösbar, wenn sie es zweimal für n-1 Scheiben ist. Hieraus folgt, dass jede Scheibe doppelt so oft bewegt wird wie die nächstbreitere. Die Rekursion baut einen vollständigen Binärbaum der Tiefe n an Funktionsaufrufen auf, wobei in jedem Knoten 1 Zug stattfindet, so dass sich insgesamt 2n-1 Züge und somit ein exponentieller Aufwand ergeben.

Sind die Scheiben von oben nach unten mit Farbe1, …, Farben eingefärbt, dann enthält in jedem Funktionsaufruf der Parameter $num den Farbindex der zu bewegenden Scheibe. Dies lässt sich auf andere Eigenschaften übertragen. Um z. B. die Ausgabe an einen Stapel von (bis zu) 8 Euro-Münzen anzupassen, genügt es, im obigen Quellcode einfach nur das Cmdlet Write-Host etwas anders zu parametrisieren:

    Write-Host (" 1 Cent", " 2 Cent", "10 Cent", " 5 Cent", "20 Cent",
           " 1 Euro", "50 Cent", " 2 Euro")[$num] "von $src nach $dst"

Alles ist im Fluss … aber in welchem?

Wer erfahren im Lesen von PowerShell-Ausdrücken ist, ahnt vielleicht auch ohne Ausprobieren, welche Ausgabe der folgende Einzeiler (für PowerShell V4 oder höher) bewirkt:

,'M'+"$('p','s')"[@(2)+-1..1].ForEach({"$_".Trim()*2})-Join[Char](3*5*7)

Alle Angaben sind ohne jegliche Gewähr. Der Autor übernimmt keinerlei Verantwortung oder Haftung für Fehler oder Ungenauigkeiten, die in diesem Dokument auftreten können. Anmerkungen, Ergänzungen und Verbesserungsvorschläge zu diesem Artikel werden gerne entgegengenommen.