Python :: Aufgabe #234 :: Lösung #1
2 Lösungen

#234
Das Problemspiel der spagetti-essenden Philosophen
Anfänger - Python
von hollst
- 04.12.2019 um 18:44 Uhr
wiki
https://de.wikipedia.org/wiki/Philosophenproblem
Fünf Philosophen sitzen um einen runden Tisch, vor jedem ein Teller voll mit Spagetti und links davon eine Gabel (BILD 1).
Um von dem Teller Spagetti essen zu können, benötigt man allerdings zwei Gabeln. Die zweite Gabel kann man sich von seinem
rechten Nachbarn borgen, sofern dieser nicht gerade selber beim Essen ist. Außerdem sollte man seine eigene Gabel auch nicht gerade
an seinen linken Nachbarn verborgt haben.
Das Spiel beginnt beim ersten Philosophen und geht Schritt für Schritt wie folgt reihum:
Jeder Philosoph kann sich in einem von drei möglichen Zuständen befinden,
u. z. denkend (Zustand 1, zu Beginn alle fünf Philosophen), hungernd (2) oder essend (3).
Ein denkender Philosoph kann per Zufallsentscheidung entweder in diesem Zustand 1 verweilen (dann geht der Zeiger sofort weiter an
seinen rechten Nachbarn) oder sich für "Essenwollen" entscheiden (Übergang von Zustand 1 in Zustand 2). In diesem Fall muss er prüfen,
ob ihm zwei Gabeln zur Verfügung stehen. Wenn Ja geht er von Zusand 2 in den Zustand 3 über und kann (sagen wir) zwei [Variable E wie Essen] Runden lang essen. Nach den zwei [E] Runden wird er zunächst wieder zum denkenden Philosophen (Zustand 1), der geleerte Teller wird sofort wieder aufgefüllt, falls der Philosoph erneut Hunger bekommt.
Hat er allerdings keine zwei Gabeln zur Verfügung, bleibt er die Runde hungrig (er verharrt im Zustand 2). Bekommt ein Philosoph
(sagen wir) zehn [Variable T wie Tod] Runden lang hintereinander nichts zu essen, ist er verhungert und das Gelage auch zu ende.
Die Frage lautet, nach wie vielen Runden (abhängig von E und T) ist im Durchschitt der erste Philosoph verhungert. Viel Spaß!
https://de.wikipedia.org/wiki/Philosophenproblem
Fünf Philosophen sitzen um einen runden Tisch, vor jedem ein Teller voll mit Spagetti und links davon eine Gabel (BILD 1).
Um von dem Teller Spagetti essen zu können, benötigt man allerdings zwei Gabeln. Die zweite Gabel kann man sich von seinem
rechten Nachbarn borgen, sofern dieser nicht gerade selber beim Essen ist. Außerdem sollte man seine eigene Gabel auch nicht gerade
an seinen linken Nachbarn verborgt haben.
Das Spiel beginnt beim ersten Philosophen und geht Schritt für Schritt wie folgt reihum:
Jeder Philosoph kann sich in einem von drei möglichen Zuständen befinden,
u. z. denkend (Zustand 1, zu Beginn alle fünf Philosophen), hungernd (2) oder essend (3).
Ein denkender Philosoph kann per Zufallsentscheidung entweder in diesem Zustand 1 verweilen (dann geht der Zeiger sofort weiter an
seinen rechten Nachbarn) oder sich für "Essenwollen" entscheiden (Übergang von Zustand 1 in Zustand 2). In diesem Fall muss er prüfen,
ob ihm zwei Gabeln zur Verfügung stehen. Wenn Ja geht er von Zusand 2 in den Zustand 3 über und kann (sagen wir) zwei [Variable E wie Essen] Runden lang essen. Nach den zwei [E] Runden wird er zunächst wieder zum denkenden Philosophen (Zustand 1), der geleerte Teller wird sofort wieder aufgefüllt, falls der Philosoph erneut Hunger bekommt.
Hat er allerdings keine zwei Gabeln zur Verfügung, bleibt er die Runde hungrig (er verharrt im Zustand 2). Bekommt ein Philosoph
(sagen wir) zehn [Variable T wie Tod] Runden lang hintereinander nichts zu essen, ist er verhungert und das Gelage auch zu ende.
Die Frage lautet, nach wie vielen Runden (abhängig von E und T) ist im Durchschitt der erste Philosoph verhungert. Viel Spaß!
#1

von Klaus (1960 Punkte)
- 22.02.2020 um 12:41 Uhr
Der Code ist besonders ausführlich, um exemplarisch den Einsatz der OOP zu zeigen. Die Konstanten können modifiziert und damit verschiedene Szenarien simuliert werden.
Python-Code

import random as rnd # Definition der Konstanten / diese können modifiziert werden # Anzahl der Philosophen am Tisch NUMBER_OF_PHILOSOPHER = 5 # Wahrscheinlichkeit, mit der der denkende Philosoph zu essen beginnt RANDOM_EATING_FACTOR = 0.5 # Anzahl an Runden, die ein essender Philosoph isst EATING_ROUNDS = 2 # Anzahl der Runden, bis ein hungernder Philosoph stirbt DEATH_ROUND = 10 # Anzahl der Durchläufe, die simuliert werden NUMBER_OF_ITERATIONS = 1000 # Anzeige jeder einzelnen Runde / alternativ: Anzeige eines Fortschrittbalkens OUTPUT_PER_ROUND = False # Anzahl der Durchläufe, ab denen der Fortschrittsbalken angezeigt wird SHOW_PROGRESS_BAR_ROUNDS = 200 # Beeinflusst die Aktion eines Philosophen, der hungrig ist, aber nicht beide Gabeln hat # 'both' - er sichert sich sowohl die rechte, als auch die linke Gabel # 'left' - er sichert sich nur die linke Gabel # 'right' - er sichert sich nur die rechte Gabel # 'none' - er sichert sich keine Gabel SAVE_FORK = 'both' # Legt fest, ob alle SAVE_FORK-Varianten nacheinander simuliert werden oder nicht SIMULATE_ALL_VARIANTS = True class Philosopher: """ Stellt einen einzelnen Philosophen dar Attribute: name (string) - Name des Philosophen right_neighbour (Philosopher) - rechts nebensitzender Philosoph free_left_fork (bool) - ist die linke Gabel verfügbar? got_left_fork (bool) - hat er eine linke Gabel in der Hand? got_right_fork (bool) - hat er eine rechte Gabel in der Hand? state (string) - aktuelle Zustandsbeschreibung ('thinking' / 'hungry' / 'eating') eating_round (int) - Anzahl der Runden, die der Philosoph bereits isst hungry_round (int) - Anzahl der Runden, die der Philosoph bereits hungert Methoden: start_eating() - Philosoph beginnt mit dem Essen stop_eating() - Philosoph beendet das Essen save_fork() - Philosoph sichert sich, wenn er nicht essen kann, schon einmal die Gabel entsprechend der Konstanten SAVE_FORK """ def __init__(self, index): self.name = f'Philosoph_{index+1}' self.right_neighbour = None self.free_left_fork = True self.got_left_fork = False self.got_right_fork = False self.state = 'thinking' self.eating_round = 0 self.hungry_round = 0 def start_eating(self): self.state = 'eating' self.free_left_fork = False self.got_left_fork = True self.right_neighbour.free_left_fork = False self.got_right_fork = True self.eating_round = 1 self.hungry_round = 0 def stop_eating(self): self.state = 'thinking' self.free_left_fork = True self.got_left_fork = False self.right_neighbour.free_left_fork = True self.got_right_fork = False self.eating_round = 0 def save_fork(self): if SAVE_FORK == 'none': # Es wird sich keine Gabel gesichert pass if SAVE_FORK == 'left' or SAVE_FORK == 'both': # Es wird sich die linke Gabel schon einmal gesichert if self.free_left_fork: self.free_left_fork = False self.got_left_fork = True if SAVE_FORK == 'right' or SAVE_FORK == 'both': # Es wird sich die rechte Gabel schon einmal gesichert if self.right_neighbour.free_left_fork: self.right_neighbour.free_left_fork = False self.got_right_fork = True class Table: """ Stellt den Tisch mit Philosophen dar Attribute: table_participants (list) - Liste der am Tisch sitzenden Philosophen number_of_active_philosopher (int) - Nummer des agierenden Philosophen number_of_rounds (int) - Nummer der aktuellen Runde Methoden: participants_take_place() - Philosophen werden der Liste table_participants zugewiesen look_at_right_neighbour() - jedem Philosoph wird sein rechter Nachbar als Attribut zugeordnet force_action() - der aktive Philosoph führt seine Aktion entsprechend der Bedingungen durch next_philosopher() - der nächste Philosoph ist an der Reihe, ggf. Erhöhung der Rundenzahl """ def __init__(self): self.table_participants = [] self.number_of_active_philosopher = NUMBER_OF_PHILOSOPHER - 1 self.number_of_rounds = 0 def participants_take_place(self): # die Philosophen setzen sich an den Tisch for index in range(NUMBER_OF_PHILOSOPHER): self.table_participants.append(Philosopher(index)) def look_at_right_neighbour(self): # die Philosophen registieren ihren rechten Nachbarn for index in range(NUMBER_OF_PHILOSOPHER - 1): self.table_participants[index].right_neighbour = self.table_participants[index+1] # vom letzten Philosophen ist der erste Philosoph der rechte Nachbar self.table_participants[NUMBER_OF_PHILOSOPHER - 1].right_neighbour = self.table_participants[0] def force_action(self): active_philosopher = self.table_participants[self.number_of_active_philosopher] # es ist ein denkender Philosoph if active_philosopher.state == 'thinking': # Essensentscheidung als Zufallsereignis eating_decision = rnd.random() # Philosoph entscheidet sich zu essen if eating_decision < RANDOM_EATING_FACTOR: # Zwei Gabeln stehen zur Verfügung if (active_philosopher.free_left_fork or active_philosopher.got_left_fork) and \ (active_philosopher.right_neighbour.free_left_fork or active_philosopher.got_right_fork): active_philosopher.start_eating() else: active_philosopher.state = 'hungry' active_philosopher.hungry_round = 1 # Selektion, ob eine freie Gabel sich schon gesichert wird, obwohl noch nicht gegessen werden kann active_philosopher.save_fork() # Philosoph isst nicht, sondern denkt weiter else: # es passiert nichts pass # Aktiver Philosoph ist in beiden Fällen nicht gestorben return False # es ist ein essender Philosoph elif active_philosopher.state == 'eating': # Anzahl an Essensrunden noch nicht erreicht if active_philosopher.eating_round <= EATING_ROUNDS: active_philosopher.eating_round += 1 # Anzahl an Essensrunden wurde erreicht else: active_philosopher.stop_eating() # Aktiver Philosoph ist in beiden Fällen nicht gestorben return False # es ist ein hungriger Philosoph elif active_philosopher.state == 'hungry': # jetzt stehen Gabeln zur Verfügung if (active_philosopher.free_left_fork or active_philosopher.got_left_fork) and \ (active_philosopher.right_neighbour.free_left_fork or active_philosopher.got_right_fork): active_philosopher.start_eating() # es stehen weiterhin keine Gabeln zur Verfügung else: active_philosopher.hungry_round += 1 active_philosopher.save_fork() if active_philosopher.hungry_round >= DEATH_ROUND: return True def next_philosopher(self): self.number_of_active_philosopher += 1 if self.number_of_active_philosopher >= NUMBER_OF_PHILOSOPHER: self.number_of_rounds += 1 self.number_of_active_philosopher = 0 class Simulation: """ Steuert die Simulation Attribute: actual_iteration (int) - Anzahl des aktuellen Durchlaufes number_of_rounds_overall (int) - Anzahl der Runden über alle Durchläufe hinweg philosopher_is_dead (bool) - ist ein Philosoph schon tot? table (Table) - Verwaltet den Tisch mit den Philosophen Methoden: start_simulation() - Führt die Simulation entsprechend der iteration aus """ def __init__(self): self.actual_iteration = 0 self.number_of_rounds_overall = 0 self.philosopher_is_dead = False self.table = None # Durchläuft die Anzahl an Iterationen for index in range(NUMBER_OF_ITERATIONS): self.start_simulation() # Ausgabe der Durchschnittswerte print(f'Im Mittelwert der {NUMBER_OF_ITERATIONS} Versuche verstarb der erste Philosoph in Runde ' f'{int(self.number_of_rounds_overall / NUMBER_OF_ITERATIONS)}.\n') def start_simulation(self): self.actual_iteration += 1 self.table = Table() self.table.participants_take_place() self.table.look_at_right_neighbour() # solange kein Philosoph gestorben ist while not self.philosopher_is_dead: self.table.next_philosopher() # die Methode force_action gibt zurück (bool), ob ein Philosoph gestorben ist self.philosopher_is_dead = self.table.force_action() # für die nächste Iteration wird der Wert zurückgesetzt self.philosopher_is_dead = False self.number_of_rounds_overall += self.table.number_of_rounds # Ausgabe der Ergebnisse der jeweiligen Runde if OUTPUT_PER_ROUND: print(f'Durchgang {self.actual_iteration:4}: ' f'{self.table.table_participants[self.table.number_of_active_philosopher].name} ist in Runde ' f'{self.table.number_of_rounds} gestorben.') # Anzeige eines Fortschrittbalkens, dann Ausgabe der Mittelwerte elif NUMBER_OF_ITERATIONS >= SHOW_PROGRESS_BAR_ROUNDS: if self.actual_iteration == 1: print('FORTSCHRITT (je 10%) ', end='') # alle 10% wird ein Punkt gesetzt if self.actual_iteration % int(NUMBER_OF_ITERATIONS/10) == 0: print('. ', end='') if self.actual_iteration == NUMBER_OF_ITERATIONS: print('FERTIG') if __name__ == '__main__': if SIMULATE_ALL_VARIANTS: iterations = ['none', 'left', 'right', 'both'] translate_iterations = {'none': 'keine Gabel', 'left': 'nur die linke Gabel', 'right': 'nur die rechte Gabel', 'both': 'beide Gabeln'} for element in iterations: SAVE_FORK = element print(f'VARIANTE: Der Philosoph sichert sich {translate_iterations[element]}, wenn er hungrig ist, aber ' f'wegen fehlender Gabeln nicht essen kann.') simulation = Simulation() else: simulation = Simulation()
Kommentare:
Für diese Lösung gibt es noch keinen Kommentar
Seite 1 von 0
1