Ende letzten Monats habe ich zum Lösen des Reis-Rätsels aufgerufen. Die Gewinnerin wurde bereits ermittelt (tatsächlich gibt es nur eine einzige) und wir wollen sie gebührend feiern! Selbstverständlich gibt’s die Auflösung gleich mit dazu.

TL;DR
– Amrei löst als einzige das Reis-Rätsel und bekommt ein original Reis-Glas aus der ersten Runde des Experiments
– Die dritte Stelle des vierstelligen Codes bestimmte die Identität des Glases: 0, 4, 6, 8 und 9 als dritte Ziffer waren Trottel, 1, 2, 3, 5 und 7 Danke-Gläser.
– Das heißt: Zahlen, die einen „Kringel“ eingeschlossen haben (oder ein Dreieck, wie im Fall der 4) waren Trottel. Zahlen, die „offen“ sind und deren Linien nicht aneinander stoßen, waren Danke-Gläser
– am Ende gibt’s den Code, wie ich selbst das Rätsel in R gelöst habe

Sie lebe hoch!

Und hier ist sie: Die Einzige, die sich mit dem geknackten Reis-Code gemeldet hat. Natürlich gab’s zur Belohnung ein Reis-Zertifikat als Belohnung, zusammen mit einem original Reis-Glas aus der ersten Runde des Reis-Experiments.

 

Die Lösung

In Kürze: War die dritte Stelle des vierstelligen Codes eine 0, 4, 6, 8, oder 9, war das Glas ein Trottel. Stand eine 1, 2, 3, 5 oder 7 an dritter Stelle, war es ein Danke-Glas. Seht ihr das Muster? Die Zahlen der Trottel-Gläser haben alle ein „Loch“, schließen also einen Raum durch ihre Linie ein. Die Zahlen der Danke-Gläser sind „offen“; es gibt keine geschlossenen Formen. Die anderen drei Zahlen des Codes haben keinerlei Bedeutung. Das war’s auch schon! Auf den ersten Blick zu erkennen, wenn man’s weiß, aber wenn man nicht eingeweiht ist, verwirren die bedeutungslosen „Distraktorzahlen“ drumherum.

Amrei hat das durch cleveres Nachdenken und Ausprobieren herausgefunden. In mühsamer „Handarbeit“. Ich habe mir es ein klein wenig einfacher gemacht, indem ich zwar denselben Gedankengang verfolgt habe, aber das Ausprobieren R überlassen habe. Meinen sehr einfachen Code teile ich mich euch.
Für alle, denen R oder Programmier-Logik nicht geläufig sein sollte: Alle Zeilen, die mit # beginnen, sind Kommentare von mir. Das ist vielleicht ein bisschen verwirrend, weil ich ja sowieso im Blog beschreibe, was ich gemacht habe. Aber auch die „Antworten“, die das Programm mir zurück gibt, habe ich euch stellenweise als Kommentar eingefügt.

Reis-Rätsel mit R

Ich habe angefangen, wie jeder andere das hätte tun müssen, wenn er über meinen Blog am Rätsel teilnehmen wollte: Ich habe mir den Datensatz in R geladen, wie er auf dem Blog zur Verfügung stand. Dabei teile ich R mit, dass das es alle Werte, zwischen denen ein Tab steht, in eigene Spalten sortieren soll.

Hier übrigens noch einmal der reis_raetsel.txt.

rr <- read.csv("reis_raetsel.txt", sep = "\t")

Nun, da die Daten eingelesen sind, sehe ich mir an, ob zufälligerweise IDs in Runde 1 und 2 vergeben wurden. Die folgende Funktion gibt mir aus, welche IDs doppelt vorhanden sind.

# gibt einen Dataframe aus: ID und Häufigkeit
n_occur <- data.frame(table(rr$ID)) n_occur[n_occur$Freq > 1,]

So finde ich heraus: ID 2349 kommt in beiden Läufen vor. Es ist zwar nicht unbedingt nötig, aber ich will den überflüssigen Fall aus dem Dataframe entfernen. Daher lasse ich mir die Zeile anzeigen, in der diese ID das erste Mal auftaucht.

match(2349, rr$ID)
# [1] 18

Zeile 18 soll gehen. Das erreiche ich, indem ich R mitteile, dass rr von nun an die Daten von rr enthalten soll – aber minus Zeile 18.

rr <- rr[-18,]

Der Übersicht halber möchte ich den Dataframe der ID nach aufsteigend sortieren. Das Paket dplyr hilft mir dabei.

library(dplyr)
rr <- arrange(rr, ID)

Ich überlege mir auch, dass ich „danke“ und „Trottel“ lieber als numerische Werte anstatt als Textbeschreibung haben will. Wer weiß, ob ich’s brauchen kann. Ich hänge eine neue Spalte an meinen Dataframe und setze mit dieser simplen Funktion eine 0, wenn das Glas ein Danke-Glas ist. Und eine 1, wenn es sich um einen Trottel handelt.

rr$zahlen <- ifelse(rr$Bedingung == "danke", 0, 1)

Jetzt geht’s ans Nachdenken. Der Code muss binnen weniger Sekunden auf den ersten Blick entschlüsselbar sein. Rechnungen wie die Quersumme brauchen bereits viel zu viel Zeit. Es muss noch einfacher sein. Erste Idee: gerade oder ungerade? Kann man auch leicht per Augenmaß überprüfen, aber ich erstelle mir eine Spalte in meinem Dataframe, die kodiert, ob eine Zahl gerade ist (TRUE) oder ungerade (FALSE). Das bestimme ich, indem ich R frage, ob beim Teilen der Zahl durch 2 ein Rest bleibt (genau genommen frage ich, ob der Rest = 0 ist).

rr$even <- rr$ID %% 2 == 0

Das sieht nicht gut aus. Wenn ich die Daten betrachte, sehe ich schnell, dass gerade Zahlen sowie ungerade als „danke“ oder „Trottel“ klassifiziert werden. Wäre auch zu einfach gewesen.

Verschaffen wir uns erst einmal einen Überblick durch einen Plot. Daten zu visualisieren ist immer eine gute Idee, um Muster zu erkennen. Hier sehe ich mir an, ob die der ID, also der Reis-Code, über die Bedingungen hinweg gleichmäßig verteilt ist. Oder ob größere Zahlen beispielsweise häufiger Trottel sind.

plot(rr$ID, rr$zahlen)

Das sieht alles sehr gleichmäßig aus. Es scheint keine verdächtigen Häufungen an irgendeiner Stelle zu geben. Wenn ich nur drüber schaue, reicht so ein „quick and dirty“ Plot. Aber für den Blog darf es natürlich auch ein bisschen schicker sein. Ich habe mal eine ästhetischere Übersicht mit dem Paket ggplot gebastelt.

library(ggplot2)

ggplot(data = rr, aes(x = ID, fill = factor(zahlen))) +
geom_histogram(breaks=seq(1000, 10000, by = 200), alpha = .8, position = "dodge") +
scale_color_manual(values=c("#9e0000", "#2d2d2d")) +
scale_fill_manual(labels = c("danke", "Trottel"), values=c("#9e0000", "#2d2d2d"),
name = "Bedingung") +
theme(legend.position="top")

Hier sieht es beinahe schon so aus, als gäbe es an bestimmten Stellen mehr Trottel als Danke-Gläser oder umgekehrt. Schwer zu sagen. Wir müssen kleinschrittiger vorgehen.

Ich überlege, ob es an nur einem Teil der Zahlen hängt. Zum Beispiel nur der letzten Zahl. Dass es sich vielleicht dann um ein Danke-Glas handelt, wenn die zweite Zahl größer ist als die erste. Oder so was. In jedem Fall muss ich die vierstellige ID in einzelne Ziffern aufsplitten. Diese Funktion zum Splitten habe ich im Netz gefunden – wer googlen kann, kann auch coden. Naja, so gut wie.
Durch „first“ und „last“ gebe ich an: Das erste Bruchstück, das ich aus dem vierstelligen Code ziehe, soll von Ziffer 1 bis 1 reichen. Mit anderen Worten: Ich ziehe die erste Ziffer heraus. Das nächste Stück reicht von Ziffer 2 bis 2 – und so weiter.

splitted <- t(sapply(rr$ID, function(x) substring(x, first=c(1,2,3,4), last=c(1,2,3,4))))

Jetzt hänge ich die herausgezogenen Ziffern an meinen Dataframe an. Noch haben die Spalten, die ich hinzufüge, keinen Namen. Die Spalte mit der ersten Ziffer nenne ich „eins“ – sie sitzt in Spalte 5 meines Dataframes.

rr <- cbind(rr, splitted)
colnames(rr)[5] <- "eins"
colnames(rr)[6] <- "zwei"
colnames(rr)[7] <- "drei"
colnames(rr)[8] <- "vier"

Jetzt können wir genauer hinsehen. Ich lasse mir eine Tabelle ausgeben, in der die Verteilung von „danke“ und „Trottel“ pro Ziffer an der ersten Stelle des Codes dargestellt wird. Für die Ziffer 0 an erster Stelle des Codes gibt es zum Beispiel 13 Danke-Gläser und 11 Trottel. Die Anzahl ist für jede mögliche Zahl an erste Stelle sehr vergleichbar. Das scheint nicht die Lösung zu sein.

table(rr$eins, rr$zahlen)

#    0 1
# 1 13 11
# 2 3 9
# 3 12 10
# 4 12 16
# 5 13 10
# 6 6 14
# 7 11 9
# 8 13 11
# 9 14 8

Dasselbe nochmal für die zweite Stelle des Codes. Auch hier sieht alles sehr ähnlich aus. 10 Danke-Gläser und 10 Trottel tragen beispielsweise die 0 an zweiter Stelle.

table(rr$zwei, rr$zahlen)

#    0 1
# 0 10 10
# 1 15 7
# 2 9 10
# 3 12 12
# 4 6 8
# 5 10 13
# 6 11 9
# 7 8 8
# 8 6 12
# 9 10 9

Und bäm – ich habe noch gar nicht damit gerechnet, da stolpere ich über die Lösung! An der dritten Stelle teilen sich die Gläser sehr eindeutig auf. Die 0 tragen zum Beispiel nur Trottel an dritter Stelle. Die 1 nur Danke-Gläser.

## LOESUNG
table(rr$drei, rr$zahlen)

#   0 1
# 0 0 11
# 1 16 0
# 2 17 0
# 3 20 0
# 4 0 22
# 5 24 0
# 6 0 15
# 7 20 0
# 8 0 21
# 9 0 29

Ich stelle also fest:

# 0, 4, 6, 8, 9 --> Trottel
# 1, 2, 3, 5, 7 --> danke

So weit, so gut. Aber wieso ausgerechnet diese Aufteilung? Gibt es einen Sinn dahinter oder ist das zufällig? Hier kann mir R nicht mehr helfen. Jetzt gibt es nur noch davor sitzen und auf die Eingebung warten. Die kommt dann auch: Bereits zu Beginn hatte ich eines dieser Rätsel im Kopf, das häufiger mal durch meinen Newsfeed gegeistert ist. Dabei ging es um Rechenaufgaben, bei denen man anstatt der Zahlenwerte die „Kreise“ addieren musste, die die Ziffern eingeschlossen haben. Eine 8 zum Beispiel hat zwei „Löcher“. Eine 0 eins; ist ja im Prinzip ein großer Kringel. Auch eine 9 und eine 6 haben einen. Alles Zahlen, die bei unseren Reisgläsern einen Trottel charakterisieren! Dann ist da noch die 4. Die hat zwar keinen Kringel, aber schließt ebenfalls eine Form ein: Ein Dreieck. Super, wie gut das auskommt: Jede Bedingung wird durch 5 Zahlen bestimmt, sodass die Verteilung ganz natürlich absolut gleichmäßig ist.
Damit haben wir’s auch. Natürlich hätte ich R dafür nicht gebraucht und Amrei hat bewiesen, dass es auch ohne geht. Aber dadurch, dass ich mir die Daten schnell und übersichtlich darstellen konnte, konnte ich mich allein auf die Denkarbeit konzentrieren, ohne irgendwann vor lauter Zahlen-Abgleich den Wald vor lauter Bäumen nicht mehr zu sehen.

Das Schöne an dieser Kodierung: Theoretisch könnte man sie sogar noch mal verwenden. Dafür müsste man nur die relevante Ziffer variieren – dann ist es beim nächsten Mal eben nicht die dritte Stelle, sondern die erste. Der Schimmel-Bewerter darf natürlich nicht wissen, welche es ist.

Weil R kostenlos ist und ausprobieren auch, kann ich euch nur dazu ermuntern, den Kram nachzumachen (Copy-Paste reicht hier, um die Ergebnisse zu reproduzieren).