Tkinter Arc für Monte Carlo Simulation

Fragen zu Tkinter.
Antworten
steveroch_rs
User
Beiträge: 7
Registriert: Dienstag 3. November 2015, 20:26

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? :lol:

Vielen Dank

Steve


EDIT:

Könnte ich den Arc auch vom Canvas lösen und in einen Container packen und den Container dann verschieben?
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

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()

BlackJack

@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‽
steveroch_rs
User
Beiträge: 7
Registriert: Dienstag 3. November 2015, 20:26

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‽
Hi,

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()
Was mir ebenfalls noch aufgefallen ist, wieso werden die "Ecken" von meinem Arc abgeschnitten wenn ich die bbox genau auf die Fenstergröße setze? Dann müsste doch eigentlich der Arc genau reinpassen und nicht überstehen. Woran liegt das und wie kann ich das lösen?

Danke

Steve
steveroch_rs
User
Beiträge: 7
Registriert: Dienstag 3. November 2015, 20:26

__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()

Hi,

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
BlackJack

@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.
steveroch_rs
User
Beiträge: 7
Registriert: Dienstag 3. November 2015, 20:26

BlackJack 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.
@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 nicht :(
Könntest du vielleicht so freundlich sein und mir zeigen wie man einen Arc ordentlich gestaltet, so dass er verschiebbar ist?
BlackJack

@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
User
Beiträge: 7
Registriert: Dienstag 3. November 2015, 20:26

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.
@BlackJack: Danke jetzt hab ichs kapiert :) man muss einfach das Rechteck gedacht außerhalb des Fensters platzieren. Super kompliziert :mrgreen: wär doch viel besser wenn man das mit Halbachsen einer Ellipse macht und den Mittelpunkt wählen lässt.
BlackJack

@steveroch_rs: Kannst Dir ja eine Funktion schreiben die das macht, also dann in die Koordinaten die Tk erwartet umrechnet. :-)
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

steveroch_rs hat geschrieben:
@BlackJack: Danke jetzt hab ichs kapiert :) man muss einfach das Rechteck gedacht außerhalb des Fensters platzieren. Super kompliziert :mrgreen: 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...
BlackJack

Ist ja schon geklärt, aber ich hatte hier immer noch die Skizze für diesen Fall rumliegen:
Bild
steveroch_rs
User
Beiträge: 7
Registriert: Dienstag 3. November 2015, 20:26

BlackJack hat geschrieben:Ist ja schon geklärt, aber ich hatte hier immer noch die Skizze für diesen Fall rumliegen:
Bild
@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.

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? :mrgreen:

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
BlackJack

@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:

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()
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.
steveroch_rs
User
Beiträge: 7
Registriert: Dienstag 3. November 2015, 20:26

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:

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()
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.
Wow vielen Dank dass du dir so viel Mühe gegeben hast ich werde mir das mal in Ruhe zu Gemüte führen.
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! :D
BlackJack

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()
Antworten