Die Mehrfachalternative in R: die Funktion switch()

Die Mehrfachalternative lässt sich durch verschachtelte Alternativen realisieren, was aber oft zu umständlichen und fehleranfälligen Quelltexten führt. Übersichtlicher ist die Realisierung mit der Funktion switch(). Für sie gibt es in R zwei Versionen, die sich in der Bedingungsprüfung unterscheiden: entweder wird eine Zeichenkette ausgewertet (character-Version) oder das Ergebnis der Bedingungsprüfung wird in eine ganze Zahl verwandelt (integer-Version).

Einordnung des Artikels

Einführung

Bei einer Alternative kann eine von zwei Möglichkeiten eintreten; welche Möglichkeit eintritt, wird durch eine Bedingungsprüfung festgestellt. Und abhängig davon welche Möglichkeit eintritt, werden andere Programmteile ausgeführt (siehe Abbildung 1 links; je nachdem ob die Bedingung erfüllt ist oder nicht, wird Anweisung 2 oder Anweisung 3 ausgeführt).

Abbildung 1: Links: Darstellung der Alternative, bei der durch eine Bedingungsprüfung entschieden wird, welche von 2 Anweisungen ausgeführt wird. Rechts: Bei der Mehrfachalternative wird nach der Bedingungsprüfung einer von im Prinzip beliebig vielen Zweigen ausgewählt (hier sind es 4 Zweige).Abbildung 1: Links: Darstellung der Alternative, bei der durch eine Bedingungsprüfung entschieden wird, welche von 2 Anweisungen ausgeführt wird. Rechts: Bei der Mehrfachalternative wird nach der Bedingungsprüfung einer von im Prinzip beliebig vielen Zweigen ausgewählt (hier sind es 4 Zweige).

Bei einer Mehrfachalternative können beliebig viele Möglichkeiten eintreten (siehe Abbildung 1 rechts; hier gibt es vier Möglichkeiten, von denen eine wieder durch eine Bedingungsprüfung ausgewählt wird). Es ist klar, dass man eine Mehrfachalternative durch geeignet viele Alternativen realisieren kann. Allerdings führt dies zu umständlichen und fehleranfälligen Quelltexten. Es gibt daher eine eigene Funktion switch(), die prüft, welche von mehreren Möglichkeiten zutrifft.

Die Implementierung der Funktion switch() hängt aber davon ab, wie die Bedingungsprüfung realisiert ist; dazu gibt es zwei Möglichkeiten:

  1. Entweder wird geprüft, welchen Wert eine ganze Zahl annimmt (man nennt dies die integer-Version von switch()).
  2. Oder es wird geprüft, welchen Wert eine Zeichenkette hat (man nennt dies die character-Version von switch()).

In den folgenden Abschnitten werden diese beiden Versionen vorgestellt, dazu wird ein sehr einfaches Beispiel gewählt:

  1. Es ist eine ganze Zahl von 10 bis 15 gegeben und es soll die zugehörige Hexadezimalziffer von A bis F bestimmt werden.
  2. Es ist die Hexadezimalziffer (von A bis F gegeben) und es soll die zugehörige Zahl (von 10 bis 15) bestimmt werden.

Die Aufgabe lässt sich auch ohne die Funktion switch() lösen – und diese Lösung ist sogar viel einfacher. Aber um kennenzulernen, wie man die Funktion switch() einsetzt, ist diese Beispiel bestens geeignet.

Umrechnung zwischen einer Zahl und einer Hexadezimalziffer

Beim Umrechnen zwischen Dezimalzahlen und Hexadezimalzahlen benötigt man die Zuordnung zwischen den Zahlen von 10 bis 15 zu den Hexadezimalziffern von A bis F, die man folgender Tabelle entnehmen kann:

# 10  ||  A 
# 11  ||  B 
# 12  ||  C 
# 13  ||  D 
# 14  ||  E 
# 15  ||  F

Da es in R die Konstanten letters und LETTERS gibt, lässt sich das Problem der Umrechnung (in beiden Richtungen) leicht lösen. Die Konstanten sind:

letters[1:6]
# [1] "a" "b" "c" "d" "e" "f"

LETTERS[1:6]
# [1] "A" "B" "C" "D" "E" "F"

Die Berechnung der Hexadezimalziffer lautet dann zum Beispiel (hier werden de Großbuchstaben verwendet):

# n als integer gegeben, zum Beispiel
n <- 10

hexdigit <- character(length = 1)
if(n > 9 && n < 16){
  hexdigit <- LETTERS[n - 9]
}
hexdigit
# [1] "A"

Die umgekehrte Berechnung könnte lauten:

# hexdigit als character gegeben, zum Beispiel

hexdigit = "A"

n <- 9 + which(LETTERS == hexdigit)
n
# [1] 10

Wie man diese beiden Umrechnungen mit der Funktion switch() durchführt, wird in den folgenden Abschnitten gezeigt.

Die integer-Version von switch()

Die Funktion switch() besitzt als Eingabewerte EXPR und ... :

switch(EXPR, ...)

Dabei steht EXPR für den Wert (oder Ausdruck) der geprüft werden soll; in der integer-Version muss hier eine ganze Zahl stehen. (Was passiert, wenn man eine Gleitkommazahl eingibt, wird weiter unten gezeigt.)

Das Argument ... gibt die verschiedenen Möglichkeiten der Mehrfachalternative an:

Man erkennt daran sofort, dass die Alternativen nicht zu einem Vektor zusammengefasst werden dürfen, denn dann gibt es nur für EXPR == 1 eine Auswahl, nämlich den Vektor.

Das folgende Skript zeigt die Realisierung für das Beispiel der Berechnung der Hexadezimalziffer:

# n als integer gegeben, zum Beispiel
n <- 10

hexdigit <- switch(EXPR = (n - 9), 'A', 'B', 'C', 'D', 'E', 'F')
hexdigit
# [1] "A"

n <- 16
hexdigit <- switch(EXPR = (n - 9), 'A', 'B', 'C', 'D', 'E', 'F')
hexdigit
# NULL

Zeile 4: Das Argument EXPR wird gleich n - 9 gesetzt; wenn daher n die Werte 10, 11, ..., 15 annimmt, ist n - 9 gleich 1, 2, ..., 6. Für das Argument ... werden die 6 Hexadezimalziffern von A bis F gesetzt.

Zeile 2, 5 und 6: Für n = 10 erhält man "A" .

Zeile 8 bis 11: Dagegen gibt es zu n = 16 keine Zuordnung im Argument ... und die Funktion switch() liefert NULL als Rückgabewert.

Naheliegend ist die Frage, welchen Wert switch() liefert, wenn oben etwa n <- 10.5 gesetzt wird:

n <- 10.5

hexdigit <- switch(EXPR = (n - 9), 'A', 'B', 'C', 'D', 'E', 'F')
hexdigit
# [1] "A"

In diesem Fall wird die Gleitkommazahl n - 9 in eine ganze Zahl verwandelt und dabei werden Nachkommastellen abgeschnitten. Das heißt EXPR wird jetzt mit 1 verarbeitet, was zum Ergebnis "A" führt.

Eine weitere Fehlerquelle ist es, statt den 6 Zeichen für ... den Vektor LETTERS[1:6] zu setzen. Das folgende Skript zeigt, wie switch() jetzt die Eingaben verarbeitet:

# n als integer gegeben, zum Beispiel
n <- 10

hexdigit <- switch(EXPR = (n - 9), LETTERS[1:6])
hexdigit
# [1] "A" "B" "C" "D" "E" "F"

n <- 15
hexdigit <- switch(EXPR = (n - 9), LETTERS[1:6])
hexdigit
# NULL

Zeile 2 bis 6: In ... gibt es nur eine Möglichkeit. Daher wird n = 10 zum kompletten Vektor LETTERS[1:6] ausgewertet.

Zeile 8 bis 11: Für n = 15 gibt es keine Zuordnung und es wird NULL zurückgegeben.

Dass NULL zurückgegeben wird, wenn keine Übereinstimmung zwischen EXPR und einer Komponente in ... besteht, möchte man gerne vermeiden und stattdessen einen default-Wert zurückgeben. In der integer-Version von switch() kann man aber keinen default-Wert setzen; dies ist nur in der character-Version möglich.

Die character-Version von switch()

In der integer-Version von switch() liefert EXPR einen Zahlenwert i, der die i-te Komponente aus den Argumenten ... auswählt. In der character-Version muss eine Zeichenkette die Zuordnung herstellen. Dies geschieht dadurch, dass die Argumente ... mit Namen versehen werden: Die Zuordnung erfolgt von der Zeichenkette zum Namen.

Das folgende Skript demonstriert dies für das Beispiel der Hexadezimalziffern. Dabei ist eine Hexadezimalziffer gegeben (von A bis F) und im Argument ... von switch() sind die 6 Zahlen (von 10 bis 15) mit Namen versehen; die Namen stimmen gerade mit den möglichen Werten von EXPR überein:

# digit als Zeichen gegeben, zum Beispiel
digit <- 'A'

n <- switch(EXPR = digit, A = 10, B = 11, C = 12, D = 13, E = 14, F = 15)
n
# [1] 10

digit <- 'F'

n <- switch(EXPR = digit, A = 10, B = 11, C = 12, D = 13, E = 14, F = 15)
n
# [1] 15

digit <- 'G'
n <- switch(EXPR = digit, A = 10, B = 11, C = 12, D = 13, E = 14, F = 15)
n
# NULL

Zeile 2 bis 5: Die Hexadezimalziffer 'A' stimmt mit dem Namen der ersten Komponente in ... überein, daher wird n gleich 10 gesetzt.

Zeile 14 bis 17: Kann die Auswertung von EXPR keinem Namen zugeordnet werden, gibt switch() NULL zurück.

Den letzten Fall möchte man oft vermeiden und stattdessen selbst einen geeigneten Wert setzen. Aber wie soll man für "alle anderen Werte" eine Zuordnung definieren? Dies geschieht mit Hilfe eines default-Wertes, der als letzter Wert von ... gesetzt wird, der aber keinen Namen erhält.

Im folgenden Skript wird NA als default-Wert gesetzt, wenn keine Zuordnung hergestellt werden kann:

digit <- 'G'

n <- switch(EXPR = digit, A = 10, B = 11, C = 12, D = 13, E = 14, F = 15, NA)
n
# [1] NA

Man könnte natürlich auch jeden anderen Wert anstelle von NA verwenden; wichtig ist nur, dass er keinen Namen erhält.

Der Rückgabewert von switch()

Die obigen Beispiele waren in dem Sinn speziell, dass stets von einer Zahl zu einem Zeichen oder umgekehrt zugeordnet wurde. Es sollte aber klar sein, dass man im Argument ... Objekte mit beliebigem Datentyp setzen kann. Es ist nicht einmal nötig, dass die Objekte in ihrem Datentyp übereinstimmen. Wenn keine Zuordnung möglich ist, wird NULL zurückgeben

x <- switch(EXPR = 1, 'A', 17)
x
# [1] "A"

y <- switch(EXPR = 2, 'A', 17)
y
# [1] 17

z <- switch(EXPR = 3, 'A', 17)
z
# NULL

Realisiert die Funktion switch() die Mehrfachalternative?

Abbildung 1 kann missverstanden werden, wenn man die Funktion switch() nicht kennt: Die Konstrollstruktur if else realisiert die Alternative, dagegen sollte man switch() als eine Funktion auffassen, die lediglich eine vereinfachte Version der Mehrfachalternative ist. Was genau mit dieser Aussage gemeint ist, wird im Folgenden erklärt.

Zunächst zur Alternative und if else: In Abbildung 1 links wird durch die Bedingungsprüfung festgestellt, welcher von zwei möglichen Fällen vorliegt, und anschließend entweder Anweisung 2 oder Anweisung 3 ausgeführt. Dabei kann anstelle einer einzigen Anweisung auch ein ganzer Block von Anweisungen stehen. An der Syntax von if else erkennt man dies an den geschweiften Klammern, die mit beliebigen Anweisungen gefüllt werden können:

if(cond){
    # Anweisungen
} else {
    # Anweisungen
}

Dagegen sollte aus den Erläuterungen zur Funktion switch() klar geworden sein, dass mit switch() keine Blöcke von Anweisungen angesteuert werden können. In anderen Programmiersprachen wird switch() etwa so implementiert, wie es das folgende Skript im Pseudocode andeuten soll:

switch(EXPR){
    case 1:
        # Block 1;
    case 2:
        # Block 2;
        
    # und so weiter
    
    default:
        # Block default
}

Hier wird ebenfalls ein Ausdruck EXPR geprüft, von dem hier angenommen wird, dass er ganze Zahlen 1, 2, ... liefert. Je nachdem, welche Zahl vorliegt, wird ein anderer Block von Operationen ausgeführt. Zuletzt wird sogar ein default-Block ausgeführt, der dann gewählt wird, wenn keine der in case 1, case 2, ... aufgeführten Zahlen vorliegt.

Diese Komplexität der Mehrfachalternative lässt sich durch switch() nicht unmittelbar realisieren. Oben wurde gezeigt, dass switch() immer nur einen Wert zurückgibt. Man kann natürlich diesen Wert verwenden, um die Ausführung eines Blockes von Anweisungen anzustoßen, etwa indem man sich eine geeignete Funktion zurechtlegt. Mit diesem zusätzlichen Schritt kann man die komplette Mehrfachalternative realisieren. Hier wird dies nicht vorgestellt, da man dazu einiges Wissen über selbst-definierte Funktionen benötigt.

Zusammenfassung

Die Funktion switch(EXPR, ...) gibt es in zwei Versionen:

  1. integer-Version
  2. character-Version.

Die Funktion switch(): integer-Version

Liefert EXPR die ganze Zahl i, so wird die i-te Komponente aus dem Argument ... ausgewählt und von switch() zurückgegeben.

switch(EXPR, c1, c2, ..., cn)

Für EXPR == i wird ci zurückgegeben.

Die Funktion switch(): character-Version

Liefert EXPR die Zeichenkette "x" , so wird aus dem Argument ... die Komponente mit Namen x ausgewählt und von switch() zurückgegeben.

switch(EXPR, x = c1, y = c2, ...)

Für EXPR == "x" wird c1 zurückgegeben.