Hallo,
ich habe folgendes Problem: Für die Seminararbeit in der Schule muss ich als Zusatz noch ein GUI Programm schreiben, dass die Monte Carlo Simulation um Pi zu berechnen bildlich zeigt. Kurz ich brauche einen Viertelkreis in den Punkte gestreut werden.
Mein Plan wäre gewesen ein Canvas mit 1000x1000 zu zeichnen, den Mittelpunkt des Viertelkreises (Arc) dann bei 0x1000 zu setzen und den Radius auf 1000 sodass es ein Viertelkreis mit Radius 1000 und Mittelpunkt in der unteren linken Ecke vom Canvas entsteht. Die Punkte hätte ich als mini Ovale die dann aussehen wie Punkte reingestreut.
Naja aus welchem Grund auch immer finde ich im Internet a) keine vernünftige Erklärung was die ersten 4 Zahlen in .create_arc() überhaupt bedeuten und b) wie ich so einen Arc verschieben kann. :K Was ich weiß ist, dass ich wenn das Canvas mit .pack() in die Mitte des Fensters gesetzt ist, auch der Arc dort ansetzt und dass die Zahlen irgendeine bbox sind.
Was hats damit jetzt genau auf sich
Gibt es irgendeine "elegantere" Lösung als Arcs und Ovals?
Vielen Dank
Steve
EDIT:
Könnte ich den Arc auch vom Canvas lösen und in einen Container packen und den Container dann verschieben?
Tkinter Arc für Monte Carlo Simulation
Ich finde die Erlaeuterungen hier http://infohost.nmt.edu/tcc/help/pubs/t ... e_arc.html eigentlich ganz schluessig. Du definierst eine bounding box, und start/extend fuer die tatsaechlichen Arc.
Code: Alles auswählen
from Tkinter import *
from Tkinter import *
master = Tk()
w = Canvas(master, width=200, height=100)
w.pack()
extent = 10
arc = w.create_arc(0, 0, 200, 100, fill="red", start=0, extent=extent)
def modify():
global extent
extent += 3
w.itemconfig(arc, extent=extent)
master.after(300, modify)
master.after(300, modify)
mainloop()
@steveroch_rs: Die Koordinaten geben die beiden Ecken des Rechtecks an in dem der Bogen gezeichnet wird, das heisst dessen Kanten der Bogen berührt. Also genau wie bei `create_oval()`. Mit dem `start`-Argument gibst Du an welcher Gradzahl der Bogen anfangen soll und mit `extent` wieviel Grad der Bogen von `start` aus gegen den Uhrzeigersinn gezeichnet werden soll. (Siehe die Doku die __deets__ verlinkt hat.)
Wieso möchtest Du etwas verschieben statt es gleich an der richtigen Stelle zu zeichnen? Dir ist klar, dass man für's zeichnen durchaus Koordinaten ausserhalb des (gerade) sichtbaren Bereichs angeben kann, inklusive negativer Koordinaten‽
Wieso möchtest Du etwas verschieben statt es gleich an der richtigen Stelle zu zeichnen? Dir ist klar, dass man für's zeichnen durchaus Koordinaten ausserhalb des (gerade) sichtbaren Bereichs angeben kann, inklusive negativer Koordinaten‽
-
- User
- Beiträge: 7
- Registriert: Dienstag 3. November 2015, 20:26
Hi,BlackJack hat geschrieben:@steveroch_rs: Die Koordinaten geben die beiden Ecken des Rechtecks an in dem der Bogen gezeichnet wird, das heisst dessen Kanten der Bogen berührt. Also genau wie bei `create_oval()`. Mit dem `start`-Argument gibst Du an welcher Gradzahl der Bogen anfangen soll und mit `extent` wieviel Grad der Bogen von `start` aus gegen den Uhrzeigersinn gezeichnet werden soll. (Siehe die Doku die __deets__ verlinkt hat.)
Wieso möchtest Du etwas verschieben statt es gleich an der richtigen Stelle zu zeichnen? Dir ist klar, dass man für's zeichnen durchaus Koordinaten ausserhalb des (gerade) sichtbaren Bereichs angeben kann, inklusive negativer Koordinaten‽
danke für die schnelle Antwort.
Das mit den Eckpunkten habe ich mittlerweile kapiert.
Jedoch benötige ich für meinen Arc die linke untere Canvas Ecke als Mittelpunkt für den Viertelkreis. Ich habe bisher keine Möglichkeit gefunden den Arc entweder gleich dort unten zeichnen zu lassen bzw. ihn dorthin zu verschieben. Wenn ich ohne etwas an den Werten zu drehen einen Arc zeichnen lasse, setzt dieser immer in der Mitte meines Canvas an wo ich ihn aber nicht gebrauchen kann.
Das ist mein Code dafür:
Code: Alles auswählen
#!/usr/bin/python
# -*- coding: utf-8 -*-
from Tkinter import *
tk = Tk()
tk.geometry('500x500+500+500')
tk.title('Monte-Carlo Simulation for Pi')
canvas = Canvas(tk, height=500, width=500, bg='grey')
arc = canvas.create_arc(0, 0, 500, 500, start=0, extent=90, fill="red")
canvas.pack()
tk.mainloop()
Danke
Steve
-
- User
- Beiträge: 7
- Registriert: Dienstag 3. November 2015, 20:26
Hi,__deets__ hat geschrieben:Ich finde die Erlaeuterungen hier http://infohost.nmt.edu/tcc/help/pubs/t ... e_arc.html eigentlich ganz schluessig. Du definierst eine bounding box, und start/extend fuer die tatsaechlichen Arc.
Code: Alles auswählen
from Tkinter import * from Tkinter import * master = Tk() w = Canvas(master, width=200, height=100) w.pack() extent = 10 arc = w.create_arc(0, 0, 200, 100, fill="red", start=0, extent=extent) def modify(): global extent extent += 3 w.itemconfig(arc, extent=extent) master.after(300, modify) master.after(300, modify) mainloop()
vielen Dank. Die Erklärungen haben mir tatsächlich geholfen zumindest die bbox zu verstehen. Jedoch konnte ich immernoch nicht herausfinden wie ich den Arc dann letztendlich positionieren kann wo ich möchte und nicht wie Tkinter/Python das über Default Verhalten tut.
Grüße
Steve
@steveroch_rs: Du sagst Du hast das mit den Koordinaten verstanden, setzt die aber dann so das der Mittelpunkt des Arc im Mittelpunkt des Canvas ist statt sie so zu setzen das der Mittelpunkt in der gewünschten Ecke ist. Was ist denn der Mittelpunkt? Welche Koordinaten hat der? Und wie müssen dann die Arc-Koordinaten aussehen das der Eckpunkt der Mittelpunkt der Arc-Koordinaten ist und das der Arc die gewünschte grösse hat. Nochmal: Koordinaten für Objekte auf dem Canvas müssen sich nicht auf den gerade sichtbaren Bereich beziehen. Also in Deinem Fall müssen die nicht zwischen 0 und 500 liegen. Man kann auch 625536 oder -20 als Koordinate angeben. Halt so wie man es braucht.
Die ”Ecken” werden abgeschnitten weil durch die „bounding box“ der *Mittelpunkt* der Linie angegeben wird, das heisst wenn die „bounding box“ mit der Canvas-Grösse übereinstimmt, dann wird an den Rändern der Teil der jenseits der Mitte der Linie liegt, also ausserhalb des angezeigten Bereichs des Canvas, nicht angezeigt. Man muss also mindestens die Hälfte der Liniendicke mehr an den Rändern zum Zeichnen bereitstellen, oder die „bounding box“ entsprechend kleiner machen.
Die ”Ecken” werden abgeschnitten weil durch die „bounding box“ der *Mittelpunkt* der Linie angegeben wird, das heisst wenn die „bounding box“ mit der Canvas-Grösse übereinstimmt, dann wird an den Rändern der Teil der jenseits der Mitte der Linie liegt, also ausserhalb des angezeigten Bereichs des Canvas, nicht angezeigt. Man muss also mindestens die Hälfte der Liniendicke mehr an den Rändern zum Zeichnen bereitstellen, oder die „bounding box“ entsprechend kleiner machen.
-
- User
- Beiträge: 7
- Registriert: Dienstag 3. November 2015, 20:26
@BlackJack: Ich weiß nichtmal wie ich Koordinaten für den Arc angeben kann. Bei mir setzt der einfach immer automatisch in der Mitte vom Fenster an. Ich meine ich hätte gelesen, das passiert weil der Arc an das Canvas gekoppelt ist. Und so etwas wie .place() gibts es ja für Canvas Objekte nichtBlackJack hat geschrieben:@steveroch_rs: Du sagst Du hast das mit den Koordinaten verstanden, setzt die aber dann so das der Mittelpunkt des Arc im Mittelpunkt des Canvas ist statt sie so zu setzen das der Mittelpunkt in der gewünschten Ecke ist. Was ist denn der Mittelpunkt? Welche Koordinaten hat der? Und wie müssen dann die Arc-Koordinaten aussehen das der Eckpunkt der Mittelpunkt der Arc-Koordinaten ist und das der Arc die gewünschte grösse hat. Nochmal: Koordinaten für Objekte auf dem Canvas müssen sich nicht auf den gerade sichtbaren Bereich beziehen. Also in Deinem Fall müssen die nicht zwischen 0 und 500 liegen. Man kann auch 625536 oder -20 als Koordinate angeben. Halt so wie man es braucht.
Die ”Ecken” werden abgeschnitten weil durch die „bounding box“ der *Mittelpunkt* der Linie angegeben wird, das heisst wenn die „bounding box“ mit der Canvas-Grösse übereinstimmt, dann wird an den Rändern der Teil der jenseits der Mitte der Linie liegt, also ausserhalb des angezeigten Bereichs des Canvas, nicht angezeigt. Man muss also mindestens die Hälfte der Liniendicke mehr an den Rändern zum Zeichnen bereitstellen, oder die „bounding box“ entsprechend kleiner machen.
Könntest du vielleicht so freundlich sein und mir zeigen wie man einen Arc ordentlich gestaltet, so dass er verschiebbar ist?
@steveroch_rs: Die Koordinaten sind die ersten vier Argumente der Methode. Dort gibst Du — wie schonmal gesagt — das Rechteck an in dem der Bogen gezeichnet wird und dessen Kanten er berührt (da wo er laut `start` und `extent` gezeichnet wird). Der Mittelpunkt ist dann in der Mitte dieses Rechtecks. Und wenn Du mit 0, 0 die linke obere Ecke auf die linke obere Ecke des Canvas legst und mit 500, 500 die rechte untere Ecke auf die rechte untere Ecke des Canvas, dann ist der Mittelpunkt halt genau im Mittelpunkt des Canvas, weil der Mittelpunkt des Rechtecks dort liegt. Wenn der woanders liegen soll, dann musst Du halt die Eckpunkte des Rechtecks das den Bogen ”umgibt” entsprechend woanders hinlegen. Und diese Koordinaten dürfen — wie schonmal gesagt — auch ausserhalb des gerade sichtbaren Canvas liegen und sie dürfen auch negativ sein. Nimm Dir am besten mal ein Blatt (Karo)Papier und zeichne Dir das auf, also ein Rechteck für den Canvas (beispielsweise 5×5 Kästchen gross), und wie dann das Rechteck für den Bogen aussehen muss, damit der Mittelpunkt dieses Rechtecks auf die linke untere Ecke des Canvas-Rechtecks fällt.
-
- User
- Beiträge: 7
- Registriert: Dienstag 3. November 2015, 20:26
@BlackJack: Danke jetzt hab ichs kapiert man muss einfach das Rechteck gedacht außerhalb des Fensters platzieren. Super kompliziert wär doch viel besser wenn man das mit Halbachsen einer Ellipse macht und den Mittelpunkt wählen lässt.BlackJack hat geschrieben:@steveroch_rs: Die Koordinaten sind die ersten vier Argumente der Methode. Dort gibst Du — wie schonmal gesagt — das Rechteck an in dem der Bogen gezeichnet wird und dessen Kanten er berührt (da wo er laut `start` und `extent` gezeichnet wird). Der Mittelpunkt ist dann in der Mitte dieses Rechtecks. Und wenn Du mit 0, 0 die linke obere Ecke auf die linke obere Ecke des Canvas legst und mit 500, 500 die rechte untere Ecke auf die rechte untere Ecke des Canvas, dann ist der Mittelpunkt halt genau im Mittelpunkt des Canvas, weil der Mittelpunkt des Rechtecks dort liegt. Wenn der woanders liegen soll, dann musst Du halt die Eckpunkte des Rechtecks das den Bogen ”umgibt” entsprechend woanders hinlegen. Und diese Koordinaten dürfen — wie schonmal gesagt — auch ausserhalb des gerade sichtbaren Canvas liegen und sie dürfen auch negativ sein. Nimm Dir am besten mal ein Blatt (Karo)Papier und zeichne Dir das auf, also ein Rechteck für den Canvas (beispielsweise 5×5 Kästchen gross), und wie dann das Rechteck für den Bogen aussehen muss, damit der Mittelpunkt dieses Rechtecks auf die linke untere Ecke des Canvas-Rechtecks fällt.
@steveroch_rs: Kannst Dir ja eine Funktion schreiben die das macht, also dann in die Koordinaten die Tk erwartet umrechnet.
steveroch_rs hat geschrieben:
@BlackJack: Danke jetzt hab ichs kapiert man muss einfach das Rechteck gedacht außerhalb des Fensters platzieren. Super kompliziert wär doch viel besser wenn man das mit Halbachsen einer Ellipse macht und den Mittelpunkt wählen lässt.
Und wie machst du dann einen Arc der halb aus dem Bild ragt? Noch ne super-spezial-Funktion mit der du erst mal selbst die Schnittpunkte mit dem Canvas-Rahmen bestimmen musst?
Das ist schon das sinnvoll das genau so zu machen - man kann natuerlich Mittelpunkt und Achsen statt bounding box nehmen, aber auch da musst du ggf. "out of the box" denken, wie man so schoen sagt. Nur bei *DEINEM* use-case halt nicht...
Ist ja schon geklärt, aber ich hatte hier immer noch die Skizze für diesen Fall rumliegen:
-
- User
- Beiträge: 7
- Registriert: Dienstag 3. November 2015, 20:26
@BlackJack: Super danke das Programm ist jetzt schon fertig ^^ habe zwar 3-5 Pixel von oben und rechts abschneiden müssen damit der arc ganz darauf sichtbar ist aber sonst passt alles.BlackJack hat geschrieben:Ist ja schon geklärt, aber ich hatte hier immer noch die Skizze für diesen Fall rumliegen:
Jetzt hätte ich nurnoch eine weitere Frage.
Zum Streuen der Punkte in den Canvas nehme ich eine while Schleife die die 2 Koordinaten berechnet und dann einen Punkt zeichnet. Allerdings geht er erst durch die Schleife ganz durch im Programm und wenn er damit fertig ist öffnet er erst das Fenster mit der fertigen Zeichnung. Wie kann ich das ändern, sodass man live zukucken kann wie die Punkte fliegen?
Code ist hier:
Code: Alles auswählen
#!/usr/bin/python
# -*- coding: utf-8 -*-
import random
import time
from Tkinter import *
tk=Tk()
def draw_dot():
dot = canvas.create_line(x_for_guidots, y_for_guidots, x_for_guidots,y_for_guidots+1, fill="red")
return
while True:
print "Pi will be calculated more precisely the more iterations you select. It will also take longer!"
loops = raw_input("Please select number of iterations (1 - 1,000,000): ")
print " "
if not loops.isdigit():
print "You must enter an integer value!"
continue
loops = int(loops)
if loops < 1:
print "Please select at least one iteration!"
continue
elif loops > 1000000:
print "The maximum amount of iterations is 1 million!"
continue
else:
print "calculating..."
print " "
#starting values
h = 0.0
counter = 0.0
loops -= 1.0
#get the current processor time and store it in 'starttime'
starttime = time.clock()
#initialize RNG
random.seed()
#Draw Tkinter Window
tk.geometry('500x500')
tk.title('Monte-Carlo Simulation for Pi')
canvas = Canvas(tk, height=500, width=500, bg='grey')
while counter <= loops:
#time.sleep()
counter += 1
x_for_guidots = random.randint(1, 500)
y_for_guidots = random.randint(1, 500)
draw_dot()
x = x_for_guidots/500.0
y = y_for_guidots/500.0
hypothenuse = ((x*x + y*y)**0.5)
if hypothenuse <= 1.0:
h += 1
hit_ratio = (h / counter)
pi = 4 * hit_ratio
arc = canvas.create_arc(-500, 3, 496, 1000, outline="green")
canvas.pack()
print " "
print "Pi: ", pi
print " "
#get the second processor time and calculate total duration
print "Time to calculate result: ", time.clock() - starttime, "seconds"
print " "
tk.mainloop()
Da ist noch haufenweise Schnickschnack von meinem Haupt-Konsolenprogramm dabei aber ich denke man verstehts.
Danke
Steve
@steveroch_rs: Ein paar Anmerkungen zum Code:
Sternchenimporte sollte man vermeiden. Es wird dann schnell unübersichtlich welcher Name wo definiert wurde. Bei `Tkinter` holt man sich weit über 100 Namen in das Modul, von denen nur ein Bruchteil wirklich benötigt wird. Ausserdem besteht die Gefahr von Namenskollisionen.
Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht normalerweise in einer Funktion die `main()` heisst. Damit kann man dann nicht mehr einfach so auf globale Variablen zugreifen → Werte betreten Funktionen und Methoden als Argumente und verlassen sie als Rückgabwerte.
Das bedeutet für `draw_dot()` das dort nicht einfach auf magische Weise von der Existenz von `canvas`, `x_for_guidots`, und `x_for_guidots` ausgegangen werden kann/darf, sondern das diese Werte als Argumente übergeben werden müssen.
Der Name `dot` wird in der Funktion zwar an einen Wert gebunden, dann aber nicht weiter benutzt, genau wie `arc` im Hauptprogramm. Das ``return`` am Ende der Funktion ist überflüssig.
Die eigentliche Programmlogik und die GUI sind vermischt — das sollten sie nicht sein. Zum Beispiel sollte die Programmlogik nicht mit GUI-Koordinaten arbeiten die dann in Werte für die Programmlogik umgerechnet werden sondern umgekehrt sollten die eigentlich interessanten Werte für die GUI umgerechnet werden. Denn wenn man das sauber trennt, dann weiss die Programmlogik gar nichts von der GUI und damit wäre es komisch wenn die Programmlogik GUI-Koordinaten erzeugt. Das Umrechnen kann und sollte man in einer GUI-Funktion erledigen und `draw_dot()` bietet sich da geradezu an.
Die äussere ``while True``-Schleife ist fehlerhaft weil das Programm nach schliessen des Fensters weiter in dieser Schleife läuft, dann aber Probleme bekommt, zum Beispiel weil das Hauptfenster nicht mehr vorhanden ist und somit auch kein neues `Canvas`-Objekt in dem nicht mehr vorhandenen Hauptfenster erzeugt werden kann. Die Schleife sollte wohl eigentlich nur die Benutzereinngabe am Anfang umfassen um sicherzustellen das der Benutzer eine sinnvolle Iterationanzahl eingibt.
Die ``continue``-Anweisungen haben allesamt keinen sichtbaren Effekt, weil die Schleife auch ohne diese Anweisungen von vorne beginnt ohne das vorher noch etwas passiert. ``continue`` verwende ich persönlich auch äusserst ungern weil das ein unbedingter Sprung an den Schleifenanfang ist den man nicht an der Struktur/Einrückung des Quelltextes sehen kann und es wird schwieriger Codeteile die ein ``continue`` enthalten in eigene Funktionen heraus zu ziehen. Ausserdem kann man ans Ende der Schleife keinen Code schreiben (jedenfalls nicht so leicht) der in jedem Fall vor dem nächsten Schleifendurchlauf ausgeführt wird, weil das von einem ``continue`` umgangen wird.
`loops` und `counter` sind irgendwie redundant und statt einer ``while``-Schleife würde sich hier eine ``for``-Schleife eher aufdrängen.
Einbuchstabige Namen sind selten sprechend genug. Ausnahmen sind ganzzahlige Schleifenzähler (`i`, `j`, `k`) oder Koordinaten (`x`, `y`) weil das aus der Mathematik bekannt ist, aber `h` für die Anzahl der Treffer ist nicht selbsterklärend.
Das initialisieren des Zufallsgenerators ist nicht nur überflüssig sondern unter bestimmten Umständen sogar kontraproduktiv. `random.seed()` ist ausserhalb des `random()`-Moduls eher für das Gegenteil von total (pseudo)zufälligen Werten gedacht, nämlich wenn man für jeden Lauf die selbe Folge von Zufallswerten braucht, kann man mit einem konkreten Wert als Argument genau das sicherstellen.
Etwas wie die `Canvas`-Grösse und davon abhängige Werte sollten nicht als ”magische” Zahlen überall im Programm verstreut stehen. Das definiert man an einer Stelle im Programm und bezieht sich dann im Rest des Quelltextes darauf. So kann man die grösse bei Bedarf an einer einzigen Stelle ändern ohne das gesamte Programm absuchen zu müssen, immer mit der Gefahr das man etwas übersieht oder inkonsistent ändert.
Ich lande dann als Zwischenergebnis ungefähr bei so etwas:
Was hier jetzt noch problematisch ist, ist die Vermischung zwischen der Simulation und der GUI. Die Simulation sollte ohne die GUI funktionieren. Dann könnte man sie auch in einen Thread auslagern oder so gestalten das die GUI sie schritt- oder ”batch”-weise mittels `after()`/`after_idle()`-Methode ausführt. Dazu wäre für eine saubere Lösung dann auch objektorientierte Programmierung nötig.
Sternchenimporte sollte man vermeiden. Es wird dann schnell unübersichtlich welcher Name wo definiert wurde. Bei `Tkinter` holt man sich weit über 100 Namen in das Modul, von denen nur ein Bruchteil wirklich benötigt wird. Ausserdem besteht die Gefahr von Namenskollisionen.
Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht normalerweise in einer Funktion die `main()` heisst. Damit kann man dann nicht mehr einfach so auf globale Variablen zugreifen → Werte betreten Funktionen und Methoden als Argumente und verlassen sie als Rückgabwerte.
Das bedeutet für `draw_dot()` das dort nicht einfach auf magische Weise von der Existenz von `canvas`, `x_for_guidots`, und `x_for_guidots` ausgegangen werden kann/darf, sondern das diese Werte als Argumente übergeben werden müssen.
Der Name `dot` wird in der Funktion zwar an einen Wert gebunden, dann aber nicht weiter benutzt, genau wie `arc` im Hauptprogramm. Das ``return`` am Ende der Funktion ist überflüssig.
Die eigentliche Programmlogik und die GUI sind vermischt — das sollten sie nicht sein. Zum Beispiel sollte die Programmlogik nicht mit GUI-Koordinaten arbeiten die dann in Werte für die Programmlogik umgerechnet werden sondern umgekehrt sollten die eigentlich interessanten Werte für die GUI umgerechnet werden. Denn wenn man das sauber trennt, dann weiss die Programmlogik gar nichts von der GUI und damit wäre es komisch wenn die Programmlogik GUI-Koordinaten erzeugt. Das Umrechnen kann und sollte man in einer GUI-Funktion erledigen und `draw_dot()` bietet sich da geradezu an.
Die äussere ``while True``-Schleife ist fehlerhaft weil das Programm nach schliessen des Fensters weiter in dieser Schleife läuft, dann aber Probleme bekommt, zum Beispiel weil das Hauptfenster nicht mehr vorhanden ist und somit auch kein neues `Canvas`-Objekt in dem nicht mehr vorhandenen Hauptfenster erzeugt werden kann. Die Schleife sollte wohl eigentlich nur die Benutzereinngabe am Anfang umfassen um sicherzustellen das der Benutzer eine sinnvolle Iterationanzahl eingibt.
Die ``continue``-Anweisungen haben allesamt keinen sichtbaren Effekt, weil die Schleife auch ohne diese Anweisungen von vorne beginnt ohne das vorher noch etwas passiert. ``continue`` verwende ich persönlich auch äusserst ungern weil das ein unbedingter Sprung an den Schleifenanfang ist den man nicht an der Struktur/Einrückung des Quelltextes sehen kann und es wird schwieriger Codeteile die ein ``continue`` enthalten in eigene Funktionen heraus zu ziehen. Ausserdem kann man ans Ende der Schleife keinen Code schreiben (jedenfalls nicht so leicht) der in jedem Fall vor dem nächsten Schleifendurchlauf ausgeführt wird, weil das von einem ``continue`` umgangen wird.
`loops` und `counter` sind irgendwie redundant und statt einer ``while``-Schleife würde sich hier eine ``for``-Schleife eher aufdrängen.
Einbuchstabige Namen sind selten sprechend genug. Ausnahmen sind ganzzahlige Schleifenzähler (`i`, `j`, `k`) oder Koordinaten (`x`, `y`) weil das aus der Mathematik bekannt ist, aber `h` für die Anzahl der Treffer ist nicht selbsterklärend.
Das initialisieren des Zufallsgenerators ist nicht nur überflüssig sondern unter bestimmten Umständen sogar kontraproduktiv. `random.seed()` ist ausserhalb des `random()`-Moduls eher für das Gegenteil von total (pseudo)zufälligen Werten gedacht, nämlich wenn man für jeden Lauf die selbe Folge von Zufallswerten braucht, kann man mit einem konkreten Wert als Argument genau das sicherstellen.
Etwas wie die `Canvas`-Grösse und davon abhängige Werte sollten nicht als ”magische” Zahlen überall im Programm verstreut stehen. Das definiert man an einer Stelle im Programm und bezieht sich dann im Rest des Quelltextes darauf. So kann man die grösse bei Bedarf an einer einzigen Stelle ändern ohne das gesamte Programm absuchen zu müssen, immer mit der Gefahr das man etwas übersieht oder inkonsistent ändert.
Ich lande dann als Zwischenergebnis ungefähr bei so etwas:
Code: Alles auswählen
# !/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
import time
import Tkinter as tk
from math import sqrt
from random import random
def draw_dot(canvas, factor, x, y):
x, y = x * factor, y * factor
canvas.create_line(x, y, x, y + 1, fill='red')
def main():
while True:
print(
'Pi will be calculated more precisely the more iterations you'
' select.\nIt will also take longer!'
)
sample_count = raw_input(
'Please select number of iterations (1 - 1,000,000): '
)
print()
if not sample_count.isdigit():
print('You must enter an integer value!')
sample_count = int(sample_count)
if sample_count < 1:
print('Please select at least one iteration!')
elif sample_count > 1000000:
print('The maximum amount of iterations is 1 million!')
else:
break
print('calculating...')
print()
start_time = time.clock()
root = tk.Tk()
root.title('Monte-Carlo Simulation for Pi')
size = 500
canvas = tk.Canvas(root, height=size, width=size, bg='grey')
hit_count = 0
for _ in xrange(sample_count):
x, y = random(), random()
draw_dot(canvas, size, x, y)
hit_count += int(sqrt(x**2 + y**2) <= 1.0)
pi = 4 * (hit_count / sample_count)
canvas.create_arc(-size, 3, size - 3, size * 2, outline='green')
canvas.pack()
print()
print('Pi:', pi)
print()
print('Time to calculate result: ', time.clock() - start_time, 'seconds')
print()
root.mainloop()
if __name__ == '__main__':
main()
-
- User
- Beiträge: 7
- Registriert: Dienstag 3. November 2015, 20:26
Wow vielen Dank dass du dir so viel Mühe gegeben hast ich werde mir das mal in Ruhe zu Gemüte führen.BlackJack hat geschrieben:@steveroch_rs: Ein paar Anmerkungen zum Code:
Sternchenimporte sollte man vermeiden. Es wird dann schnell unübersichtlich welcher Name wo definiert wurde. Bei `Tkinter` holt man sich weit über 100 Namen in das Modul, von denen nur ein Bruchteil wirklich benötigt wird. Ausserdem besteht die Gefahr von Namenskollisionen.
Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht normalerweise in einer Funktion die `main()` heisst. Damit kann man dann nicht mehr einfach so auf globale Variablen zugreifen → Werte betreten Funktionen und Methoden als Argumente und verlassen sie als Rückgabwerte.
Das bedeutet für `draw_dot()` das dort nicht einfach auf magische Weise von der Existenz von `canvas`, `x_for_guidots`, und `x_for_guidots` ausgegangen werden kann/darf, sondern das diese Werte als Argumente übergeben werden müssen.
Der Name `dot` wird in der Funktion zwar an einen Wert gebunden, dann aber nicht weiter benutzt, genau wie `arc` im Hauptprogramm. Das ``return`` am Ende der Funktion ist überflüssig.
Die eigentliche Programmlogik und die GUI sind vermischt — das sollten sie nicht sein. Zum Beispiel sollte die Programmlogik nicht mit GUI-Koordinaten arbeiten die dann in Werte für die Programmlogik umgerechnet werden sondern umgekehrt sollten die eigentlich interessanten Werte für die GUI umgerechnet werden. Denn wenn man das sauber trennt, dann weiss die Programmlogik gar nichts von der GUI und damit wäre es komisch wenn die Programmlogik GUI-Koordinaten erzeugt. Das Umrechnen kann und sollte man in einer GUI-Funktion erledigen und `draw_dot()` bietet sich da geradezu an.
Die äussere ``while True``-Schleife ist fehlerhaft weil das Programm nach schliessen des Fensters weiter in dieser Schleife läuft, dann aber Probleme bekommt, zum Beispiel weil das Hauptfenster nicht mehr vorhanden ist und somit auch kein neues `Canvas`-Objekt in dem nicht mehr vorhandenen Hauptfenster erzeugt werden kann. Die Schleife sollte wohl eigentlich nur die Benutzereinngabe am Anfang umfassen um sicherzustellen das der Benutzer eine sinnvolle Iterationanzahl eingibt.
Die ``continue``-Anweisungen haben allesamt keinen sichtbaren Effekt, weil die Schleife auch ohne diese Anweisungen von vorne beginnt ohne das vorher noch etwas passiert. ``continue`` verwende ich persönlich auch äusserst ungern weil das ein unbedingter Sprung an den Schleifenanfang ist den man nicht an der Struktur/Einrückung des Quelltextes sehen kann und es wird schwieriger Codeteile die ein ``continue`` enthalten in eigene Funktionen heraus zu ziehen. Ausserdem kann man ans Ende der Schleife keinen Code schreiben (jedenfalls nicht so leicht) der in jedem Fall vor dem nächsten Schleifendurchlauf ausgeführt wird, weil das von einem ``continue`` umgangen wird.
`loops` und `counter` sind irgendwie redundant und statt einer ``while``-Schleife würde sich hier eine ``for``-Schleife eher aufdrängen.
Einbuchstabige Namen sind selten sprechend genug. Ausnahmen sind ganzzahlige Schleifenzähler (`i`, `j`, `k`) oder Koordinaten (`x`, `y`) weil das aus der Mathematik bekannt ist, aber `h` für die Anzahl der Treffer ist nicht selbsterklärend.
Das initialisieren des Zufallsgenerators ist nicht nur überflüssig sondern unter bestimmten Umständen sogar kontraproduktiv. `random.seed()` ist ausserhalb des `random()`-Moduls eher für das Gegenteil von total (pseudo)zufälligen Werten gedacht, nämlich wenn man für jeden Lauf die selbe Folge von Zufallswerten braucht, kann man mit einem konkreten Wert als Argument genau das sicherstellen.
Etwas wie die `Canvas`-Grösse und davon abhängige Werte sollten nicht als ”magische” Zahlen überall im Programm verstreut stehen. Das definiert man an einer Stelle im Programm und bezieht sich dann im Rest des Quelltextes darauf. So kann man die grösse bei Bedarf an einer einzigen Stelle ändern ohne das gesamte Programm absuchen zu müssen, immer mit der Gefahr das man etwas übersieht oder inkonsistent ändert.
Ich lande dann als Zwischenergebnis ungefähr bei so etwas:Was hier jetzt noch problematisch ist, ist die Vermischung zwischen der Simulation und der GUI. Die Simulation sollte ohne die GUI funktionieren. Dann könnte man sie auch in einen Thread auslagern oder so gestalten das die GUI sie schritt- oder ”batch”-weise mittels `after()`/`after_idle()`-Methode ausführt. Dazu wäre für eine saubere Lösung dann auch objektorientierte Programmierung nötig.Code: Alles auswählen
# !/usr/bin/python # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function import time import Tkinter as tk from math import sqrt from random import random def draw_dot(canvas, factor, x, y): x, y = x * factor, y * factor canvas.create_line(x, y, x, y + 1, fill='red') def main(): while True: print( 'Pi will be calculated more precisely the more iterations you' ' select.\nIt will also take longer!' ) sample_count = raw_input( 'Please select number of iterations (1 - 1,000,000): ' ) print() if not sample_count.isdigit(): print('You must enter an integer value!') sample_count = int(sample_count) if sample_count < 1: print('Please select at least one iteration!') elif sample_count > 1000000: print('The maximum amount of iterations is 1 million!') else: break print('calculating...') print() start_time = time.clock() root = tk.Tk() root.title('Monte-Carlo Simulation for Pi') size = 500 canvas = tk.Canvas(root, height=size, width=size, bg='grey') hit_count = 0 for _ in xrange(sample_count): x, y = random(), random() draw_dot(canvas, size, x, y) hit_count += int(sqrt(x**2 + y**2) <= 1.0) pi = 4 * (hit_count / sample_count) canvas.create_arc(-size, 3, size - 3, size * 2, outline='green') canvas.pack() print() print('Pi:', pi) print() print('Time to calculate result: ', time.clock() - start_time, 'seconds') print() root.mainloop() if __name__ == '__main__': main()
Ich habe das Python Programmieren zugegeben schlampig im Rahmen meiner Seminararbeit gelernt und die Anforderungen zu erfüllen weshalb ich mich ganz sicher nicht als Python Programmierer bezeichnen würde da ich noch nichtmal wirklich weiß wie man mit Listen etc. richtig umgeht.
Ich werde auf jeden Fall dran bleiben und nochmal danke für den sehr ausführlichen Verbesserungsvorschlag!
Hat noch Verbesserungspotential…
Code: Alles auswählen
#!/usr/bin/python
# coding: utf8
from __future__ import absolute_import, division, print_function
import Tkinter as tk
from itertools import islice, izip
from math import sqrt
from random import random
from tkMessageBox import showerror
class Simulation(object):
def __init__(self):
random_values = iter(random, None)
self.random_coordinates = izip(random_values, random_values)
self.total_count = 0
self.hit_count = 0
def __iter__(self):
return self
def next(self):
result = x, y = next(self.random_coordinates)
self.total_count += 1
if sqrt(x**2 + y**2) <= 1:
self.hit_count += 1
return result
@property
def pi(self):
return 4 * (self.hit_count / self.total_count)
class SimulationUI(tk.Frame):
BATCH_SIZE = 1000
def __init__(self, master, canvas_size=500):
tk.Frame.__init__(self, master)
self.canvas_size = canvas_size
self.simulation = None
self.sample_count = None
self.canvas = tk.Canvas(
self,
height=self.canvas_size,
width=self.canvas_size,
background='green',
)
self.canvas.create_arc(
-self.canvas_size,
0,
self.canvas_size,
self.canvas_size * 2,
fill='blue',
outline='',
)
self.canvas.pack()
self.iteration_count_var = tk.IntVar(self, value=100000)
self.result_var = tk.StringVar(self, value='-')
frame = tk.Frame(self)
tk.Label(frame, text='# Iterations:').pack(side=tk.LEFT)
tk.Entry(
frame, textvariable=self.iteration_count_var
).pack(side=tk.LEFT)
self.start_button = tk.Button(
frame, text='Start', command=self.do_start
)
self.start_button.pack(side=tk.LEFT)
frame.pack()
tk.Label(self, textvariable=self.result_var).pack()
def plot(self, coordinate):
x, y = (c * self.canvas_size for c in coordinate)
self.canvas.create_line(x, y, x, y + 1, fill='red', tags='dot')
def do_start(self):
self.canvas.delete('dot')
try:
self.sample_count = self.iteration_count_var.get()
except ValueError:
showerror('No Number', 'Iteration count must be a number.')
else:
if self.sample_count < 1:
showerror('To Small', 'Iteration count has to be 1 or greater.')
else:
self.start_button['state'] = tk.DISABLED
self.simulation = Simulation()
self.run_simulation()
def run_simulation(self):
for coordinate in islice(
self.simulation, min(self.sample_count, self.BATCH_SIZE)
):
self.plot(coordinate)
self.sample_count = max(0, self.sample_count - self.BATCH_SIZE)
self.result_var.set(format(self.simulation.pi, '.20f'))
if self.sample_count:
self.after_idle(self.run_simulation)
else:
self.start_button['state'] = tk.NORMAL
def main():
root = tk.Tk()
root.title('Monte-Carlo Simulation for Pi')
simulation_ui = SimulationUI(root)
simulation_ui.pack()
root.mainloop()
if __name__ == '__main__':
main()