Python :: Aufgabe #228

2 Lösungen Lösungen öffentlich

Das Zahnstochermuster und die Sequenz der Anzahl freier Spitzen im Muster

Fortgeschrittener - Python von hollst - 01.10.2019 um 18:15 Uhr
Man kann sogar mit Zahnstochern Mathematik bzw. Informatik betreiben!

Ein Zahnstochermuster wird schrittweise erzeugt: Im ersten Schritt legt man einen Zahnstocher
beliebig und flach auf eine 2D-Fläche ab. Ohne besondere Einschränkungen seien angenommen, dass jeder Zahnstocher
genau die Länge 1 (eins) hat und das erste Exemplar senkrecht und mittig in einem (X, Y)-Koordinatensystem
liegt. Das Muster hat nach dem ersten Schritt zwei freie Spitzen bei (1/2, 0) und (-1/2, 0).
Im zweite Schritt werden senkrecht zum ersten Zahnstocher auf die zwei freien Spitzen jeweils ein weiterer Zahnstocher gelegt,
danach haben wir vier freie Spitzen (siehe Bild step_2). Auf diese Weise wird schrittweise fortgefahren.
Die Sequenz der Anzahl freier Spitzen im Muster beginnt so:

Schritt........: 1..2..3..4..5....6..7..8..9..10 11 12 13 14 15 16 17 18 19 ...
freie Spitzen: 2..4..4..4..8..12, 8, 4, 8, 12, 12, 16, 28, 32, 16, 4, 8, 12, 12 ...

Sie steigt zunächst an bis 12 (Schritt 6), um danach auf 4 abzufallen (Schritt 8). Dieses Auf und Ab
zieht sich stetig fort (bis in alle Ewigkeit, was noch zu beweisen wäre [jedoch nicht von uns]).
Die Entwicklung des Zahnstochermusters hat fraktalen Charakter. Anhand von Bild step_10_14_17 soll dies belegt sein.

Die Programmieraufgabe bestehe darin, die Musterentwicklung bis zum Schritt 100 "interaktiv" darzustellen (Bild step_100)
und auch die Anzahl freier Zahnstocherspitzen jeweils anzugeben.

Viel Spaß!

Lösungen:

vote_ok
von AlexGroeg (340 Punkte) - 18.10.2019 um 13:11 Uhr
Quellcode ausblenden Python-Code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Zahnstocher legen
import tkinter as tk
    
# create window, set canvas to window
window = tk.Tk()
window.resizable(False,False)
window.title('Zahnstocher legen')
c = tk.Canvas(window, width=1000, height=600, bg='white', highlightthickness=0)
c.pack()


# Variablen
x = 500 ; y = 300     # Anfangspunkt
Richtung = 1
Länge = 10
Anzahl = 1
Feld = [[ x, y]]      # 2D Listen      
Feld_neu = []
Feld_alt = []
freie_Punkte = []


def Zahnstocher(x, y, Richtung, Länge):    
    # zeichne Zahnstocher
    L = Länge/2
    if Richtung == 1:
        x0 = x ; y0 = y -L 
        x1 = x ; y1 = y +L         
    else:
        x0 = x -L ; y0 = y
        x1 = x +L ; y1 = y
    c.create_line(x0, y0, x1, y1, fill='black')        
    
    # speichre neue Endepunkte 
    Feld_neu.append([ int(x0), int(y0)])
    Feld_neu.append([ int(x1), int(y1)]) 


def Anlegen(Feld, Richtung):    
    for Punkt in Feld:
        Zahnstocher(Punkt[0], Punkt[1], Richtung, Länge)


def start():
    global Richtung
    
    Anlegen(Feld, Richtung)
    
    for e in Feld:
        Feld_alt.append(e)
    Feld.clear()
    
    # doppelte Punkte entfernen
    n = []
    for e in Feld_neu:
        if e in n:
            n.remove(e)
        else:
            if e not in Feld_alt:
                n.append(e)

    freie_Punkte.append(len(n))
    for e in n:
        Feld.append(e)
    Feld_neu.clear()     
    
    if Richtung == 1: 
        Richtung = 0
    else:       
        Richtung = 1


# RUN
s=0
while s < 100:
    start()
    s +=1
    
print(freie_Punkte)

# keep window open until user closes it
tk.mainloop() 
vote_ok
von Klaus (730 Punkte) - 11.03.2020 um 10:52 Uhr
Zur Implementierung der GUI habe ich PySimpleGUI genutzt. Basierend auf tkinter ist PySimpleGUI aus meiner Sicht eine einfache und pragmatische Lösung, um schnell eine grafische Benutzeroberfläche mit wenig Code zu kreieren.

Installation über
pip install pysimplegui
oder
conda install pysimplegui.

Alles weitere unter https://pysimplegui.readthedocs.io/en/latest/

Quellcode ausblenden Python-Code
import PySimpleGUI as sg

# Die Konstanten können je nach Bedarf angepasst werden

# Anzahl der Schritte, die durchlaufen werden sollen
NUMBER_OF_ITERATIONS = 100

# Größe des Gitters, das angezeigt werden soll (bei einer Streichholzlänge von 1)
GRID_SIZE = 55

# Zoom-Faktor, um unterschiedliche Display-Größen auszugleichen
ZOOM_FACTOR = 15

# Zeitverzögerung (in Millisekunden) der Anzeige zwischen den einzelnen Schritten
TIME_DELAY = 500


class Point:

    def __init__(self, x_coordinate, y_coordinate, state):
        """state: free, occupied"""
        self.x_coordinate = x_coordinate
        self.y_coordinate = y_coordinate
        self.state = state

    def __repr__(self):
        """zum Debuggen"""
        return f' (POINT({self.x_coordinate}/{self.y_coordinate})-{self.state}) '


class Line:

    def __init__(self, start, middle, end, orientation):
        """orientation: horizontal, vertical"""
        self.start = start
        self.middle = middle
        self.end = end
        self.orientation = orientation

    def __repr__(self):
        """zum Debuggen"""
        return f' LINE({self.start}/{self.middle}/{self.end}) - {self.orientation}) '


class Model:

    def __init__(self, start_line):
        self.list_of_points = [start_line.start, start_line.middle, start_line.end]
        self.list_of_lines = [start_line]

    def return_free_points(self):
        """gibt eine Liste der freien Punkte zurück"""
        return_list = []
        for point in self.list_of_points:
            if point.state == 'free':
                return_list.append(point)
        return return_list

    def return_number_of_free_points(self):
        """gibt die Anzahl der freien Punkte zurück"""
        return len(self.return_free_points())

    def return_new_point_orientation(self, point):
        """gibt die erforderliche Ausrichtung einer neuen Linie zurück"""
        for line in self.list_of_lines:
            if point == line.start or point == line.middle or point == line.end:
                if line.orientation == 'vertical':
                    return 'horizontal'
                elif line.orientation == 'horizontal':
                    return 'vertical'

    def check_point_exists_already(self, point):
        """prüft, ob ein Punkt bereits belegt ist und gibt diesen dann zurück"""
        for compare_point in self.list_of_points:
            if compare_point.x_coordinate == point.x_coordinate and compare_point.y_coordinate == point.y_coordinate:
                return compare_point
        return None

    def add_line(self, free_point, orientation):
        """gibt ein neues Line-Objekt basierend auf dem Ausgangspunkt und die richtige Ausrichtung zurück"""
        free_point.state = 'occupied'
        if orientation == 'vertical':
            start_point = Point(free_point.x_coordinate, free_point.y_coordinate - 0.5, 'free')
            middle_point = free_point
            end_point = Point(free_point.x_coordinate, free_point.y_coordinate + 0.5, 'free')
        elif orientation == 'horizontal':
            start_point = Point(free_point.x_coordinate + 0.5, free_point.y_coordinate, 'free')
            middle_point = free_point
            end_point = Point(free_point.x_coordinate - 0.5, free_point.y_coordinate, 'free')

        first_point = self.check_point_exists_already(start_point)
        if isinstance(first_point, Point):
            start_point = first_point
            start_point.state= 'occupied'
        else:
            self.list_of_points.append(start_point)

        second_point = self.check_point_exists_already(end_point)
        if isinstance(second_point, Point):
            end_point = second_point
            end_point.state='occupied'
        else:
            self.list_of_points.append(end_point)
        line = Line(start_point, middle_point, end_point, orientation)
        self.list_of_lines.append(line)
        return line


class GUI:

    def __init__(self):

        self.layout = [[sg.Graph(canvas_size=(GRID_SIZE * ZOOM_FACTOR, GRID_SIZE * ZOOM_FACTOR),
                    graph_bottom_left=(GRID_SIZE / -2 * ZOOM_FACTOR, GRID_SIZE / -2 * ZOOM_FACTOR),
                    graph_top_right=(GRID_SIZE / 2 * ZOOM_FACTOR, GRID_SIZE / 2 * ZOOM_FACTOR),
                    background_color='white', key='_graph_')],
                       [sg.Text('RUNDE 1: Freie Punkte 2', size=(int(GRID_SIZE*ZOOM_FACTOR/6),1), text_color='black',
                                background_color='white', justification='center', key='_text_', font=('Arial', 8))]]

        self.window = sg.Window('Zahnstochermuster', self.layout, finalize = True, background_color='white',
                                return_keyboard_events=True)
        self.graph = self.window.FindElement('_graph_')
        self.text = self.window.FindElement('_text_')

    def draw_a_line(self, line, color):
        """zeichnet eine neue Linie basierend auf dem Line-Objekt auf den Graph"""
        self.graph.draw_line((line.start.x_coordinate * ZOOM_FACTOR, line.start.y_coordinate * ZOOM_FACTOR),
                             (line.end.x_coordinate * ZOOM_FACTOR, line.end.y_coordinate * ZOOM_FACTOR),
                             color=color, width = 1)


class Controller:

    def __init__(self, number_of_iterations):
        self.number_of_iterations = number_of_iterations
        start_line = Line(Point(0, 0.5, 'free'), Point(0, 0, 'occupied'), Point(0, -0.5, 'free'), 'vertical')
        self.m = Model(start_line)
        self.new_lines=[start_line]
        self.old_lines = []
        self.gui = GUI()
        self.run()

    def run(self):
        for iteration in range(2, self.number_of_iterations + 1):
            for line in self.new_lines:
                self.gui.draw_a_line(line, 'red')
                self.old_lines = self.new_lines

            self.new_lines = []

            for free_point in self.m.return_free_points():
                new_line = self.m.add_line(free_point, self.m.return_new_point_orientation(free_point))
                self.new_lines.append(new_line)

            event, values = self.gui.window.read(timeout = TIME_DELAY)
            if event is not None:
                for line in self.old_lines:
                    self.gui.draw_a_line(line, 'black')
                for line in self.new_lines:
                    self.gui.draw_a_line(line, 'red')
                    self.gui.text(f'RUNDE {iteration}: Freie Punkte {self.m.return_number_of_free_points()}')
            if event is None:
                break

        while True:
            self.gui.text('TASTE zum Beenden')
            event, values = self.gui.window.read()

            if event is None or len(event)==1:
                print(event)
                break

        self.gui.window.close()


Controller(NUMBER_OF_ITERATIONS)