Welche Möglichkeiten der funktionalen Programmierung zur Implementierung von Conways Game of Life gibt es? [Closed]


12

Ich habe vor kurzem implementiert für Spaß Conways Spiel des Lebens in Javascript (tatsächlich aber Coffee gleiche Sache). Da Javascript als funktionale Sprache verwendet werden kann, habe ich versucht, an diesem Ende des Spektrums zu bleiben. Ich war mit meinen Ergebnissen nicht zufrieden. Ich bin ein ziemlich guter OO-Programmierer, und meine Lösung steckt voller Gleichaltriger. So lange Frage kurz: Was ist der (Pseudocode-) Funktionsstil dafür?

Hier ist Pseudocode für meinen Versuch:

class Node
  update: (board) ->
    get number_of_alive_neighbors from board
    get this_is_alive from board
    if this_is_alive and number_of_alive_neighbors < 2 then die
    if this_is_alive and number_of_alive_neighbors > 3 then die
    if not this_is_alive and number_of_alive_neighbors == 3 then alive

class NodeLocations
  at: (x, y) -> return node value at x,y
  of: (node) -> return x,y of node

class Board
  getNeighbors: (node) -> 
   use node_locations to check 8 neighbors 
   around node and return count

nodes = for 1..100 new Node
state = new NodeState(nodes)
locations = new NodeLocations(nodes)
board = new Board(locations, state)

executeRound:
  state = clone state
  accumulated_changes = for n in nodes n.update(board)
  apply accumulated_changes to state
  board = new Board(locations, state)


@Oded das geht mir deprimierend über den Kopf. Ich kenne die Grundbegriffe aber nur knapp
George Mauer

Weit über meinen Kopf hinaus ... Ich habe es nur als Beispiel dafür gepostet, was ein Meister einer Fachsprache kann. Nenn es eine Inspiration für uns alle :)
Oded

@GeorgeMauer "eigentlich Kaffee, aber das Gleiche" Dies ist ein trauriger Tag
Raynos

Antworten:


11

Nun, ein paar Ideen. Ich bin kein Experte in FP, aber ...

Es ist ziemlich klar, dass wir einen Typ haben sollten, Boardder einen Spielstatus darstellt. Die Basis der Implementierung sollte eine evolveFunktion vom Typ sein evolve :: Board -> Board; Das heißt, es entsteht ein Boardaus der Anwendung der Spielregeln auf ein Board.

Wie sollen wir umsetzen evolve? A Boardsollte wahrscheinlich eine nxm-Matrix von Cells sein. Wir könnten eine Funktion cellEvolvevom Typ implementieren, cellEvolve :: Cell -> [Cell] -> Celldie mit a Cellund seinen Nachbarn Cellden CellZustand in der nächsten Iteration berechnet .

Wir sollten auch eine getCellNeighborsFunktion implementieren , die a Cells Nachbarn aus a extrahiert Board. Ich bin mir der Signatur dieser Methode nicht ganz sicher. Abhängig davon, wie Sie implementieren Cellund was BoardSie beispielsweise haben könnten getCellNeighbors :: Board -> CoordElem -> CoordElem -> [Cell], wenn Sie ein Board und zwei Koordinaten ( CoordElemder Typ, der zum Indizieren von Positionen in a verwendet wird Board) angeben , erhalten Sie eine Liste mit variabler Länge der Nachbarn (nicht alle Zellen im Board haben die gleiche Anzahl von Nachbarn - Ecken haben 3 Nachbarn, Grenzen 5 und alle anderen, 8).

evolvekann also durch Kombinieren cellEvolveund getCellNeighborsfür alle Zellen in der Karte implementiert werden. Die genaue Implementierung hängt wiederum davon ab, wie Sie Boardund implementieren. CellEs sollte jedoch so etwas wie "für alle Zellen in der aktuellen Karte" sein, die Nachbarn abrufen und zur Berechnung der neue Zelle der Karte ". Dies sollte mit einer generischen Anwendung dieser Funktionen über die gesamte Karte unter Verwendung einer" Karte über die Zellenfunktion der Karte "möglich sein.

Andere Gedanken:

  • Sie sollten wirklich cellEvolveso implementieren , dass als Parameter ein Typ verwendet wird, GameRulesder die Regeln des Spiels codiert - beispielsweise eine Liste von Tupeln, (State,[(State,NumberOfNeighbors)],State)die für einen bestimmten Zustand und die Anzahl der Nachbarn in jedem Zustand besagt, der Zustand in der nächsten Iteration sein sollte . cellEvolvekönnte dann die signatur seincellEvolve :: GameRules -> Cell -> [Cell] -> Cell

  • Dies würde dich logischerweise dazu bringen, dich in zu evolve :: Board -> Boardverwandeln evolve :: GameRules -> Board -> Board, so dass du es evolveunverändert mit anderen verwenden GameRuleskönntest, aber du könntest einen Schritt weiter gehen und cellEvolvestattdessen pluggable machen GameRules.

  • Das Spielen mit getCellNeighborsIhnen könnte auch evolvegenerisch in Bezug auf die BoardTopologie getCellNeighborsder Karte sein - Sie könnten die Kanten der Karte, die 3D-Karte usw. umschließen.


9

Wenn Sie eine funktionale Programmierversion von Life schreiben, sind Sie es sich selbst schuldig, etwas über Gospers Algorithmus zu lernen. Es nutzt Ideen aus der funktionalen Programmierung, um dies zu erreichen Billionen von Generationen pro Sekunde auf Brettern mit Billionen von Quadraten auf einer Seite . Das hört sich unmöglich an, ich weiß, aber es ist durchaus möglich; Ich habe eine nette kleine Implementierung in C #, die quadratische Bretter 2 ^ 64 Quadrate auf einer Seite leicht handhabt.

Der Trick besteht darin, die massive Selbstähnlichkeit von Life-Boards sowohl zeitlich als auch räumlich auszunutzen. Indem Sie sich den zukünftigen Zustand großer Teile der Platine merken, können Sie große Teile auf einmal schnell vorrücken.

Ich wollte schon seit vielen Jahren eine Einführung in Gospers Algorithmus veröffentlichen, aber ich hatte nie die Zeit dafür. Wenn ich das mache, poste ich hier einen Link.

Beachten Sie, dass Sie Gospers Algorithmus für Lebensberechnungen nachschlagen möchten , nicht den Gospers Algorithmus für die Berechnung hypergeometrischer Summen.


sieht interessant aus - wartet aber immer noch auf diesen Link ...;)
jk.

3

Zufälligerweise haben wir dieses Problem heute in unserem Haskell-Vortrag behandelt. Zum ersten Mal habe ich es gesehen, aber hier ist ein Link zum Quellcode, den wir erhalten haben:

http://pastebin.com/K3DCyKj3


Würde es Ihnen etwas ausmachen, mehr darüber zu erklären, was es tut, und warum empfehlen Sie es als Antwort auf die gestellte Frage? "Nur-Link-Antworten" sind bei Stack Exchange nicht ganz willkommen
Donnerstag,

3

Vielleicht möchten Sie sich von den Implementierungen in RosettaCode inspirieren lassen.

Beispielsweise gibt es funktionale Haskell- und OCaml-Versionen, die jede Runde eine neue Karte erstellen, indem sie eine Funktion auf die vorherige anwenden, während die grafische OCaml-Version zwei Arrays verwendet und diese abwechselnd aus Geschwindigkeitsgründen aktualisiert.

Einige der Implementierungen zerlegen die Kartenaktualisierungsfunktion in Funktionen zum Zählen der Nachbarschaft, Anwenden der Lebensregel und Durchlaufen der Karte. Dies scheinen nützliche Komponenten für ein funktionales Design zu sein. Versuchen Sie, nur die Platine zu modifizieren, und behalten Sie alles andere als reine Funktionen bei.


1

Hier ist eine kurze rein funktionale Version in Clojure. Der größte Verdienst geht an Christophe Grand, der dies in seinem Blog-Beitrag " Conway's Game of Life" veröffentlicht hat

(defn neighbours [[x y]]
  (for [dx [-1 0 1] 
        dy (if (zero? dx) [-1 1] [-1 0 1])]
    [(+ dx x) (+ dy y)]))

(defn step [cells]
  (set (for [[loc n] (frequencies (mapcat neighbours cells))
             :when (or (= n 3) (and (= n 2) (cells loc)))]
         loc)))

Das Spiel kann dann durch wiederholtes Anwenden der "Schritt" -Funktion auf eine Reihe von Zellen gespielt werden, zB:

(step #{[1 0] [1 1] [1 2]})
=> #{[2 1] [1 1] [0 1]}

Die Cleverness ist der Teil (Mapcat-Nachbarzellen). Dabei wird eine Liste mit acht Nachbarn für jede der aktiven Zellen erstellt und alle zusammen verkettet. Dann kann die Häufigkeit, mit der jede Zelle in dieser Liste erscheint, gezählt werden (Frequenzen ....), und schließlich schaffen es diejenigen, die die richtigen Frequenzzählungen haben, zur nächsten Generation.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.