tkinter: Int-Werte aus Entries einlesen

Fragen zu Tkinter.
Antworten
kwon
User
Beiträge: 43
Registriert: Samstag 2. Mai 2020, 11:48

Hallo,
ich würde gerne die Integer-Werte aus Entries einlesen (durch die function readin()).
Die function SquareCreate() erzeugt die Entries und verknüpft sie mit IntVar() (habe darüber gelesen, kenne mich aber noch nicht sehr aus).
Ich habe 'place' verwendet, weil das die einzige Möglichkeit ist, die ich kenne, quatratische Entries hinzubekommen.
In Zeile 24 heißt es "list index out of range".
Was kann ich tun um erfolgreich die Werte aus den Entries einzulesen und in der Liste "bo" zu speichern?
Muss ich die Liste "sq" an readin() übergeben?
(z=SquareCreate()
readin(z)
klappt nicht)
Vielen Dank für die Hilfe im Voraus!!

kwon

Code: Alles auswählen

import tkinter as tk
from tkinter import Entry, IntVar, Tk

def quit_frame():
    main.destroy()

def SquareCreate(): 
    sq=[]
    for j in range(1,4):
        for i in range(1,4):
            t = tk.Entry(main, textvariable=IntVar(), justify="center",font=("Arial",16))
            t.place(x=i*40, y=j*40, width=40, height=40)
            t.delete(0)
            sq.append(IntVar())
    return sq

def readin(sq):
    sq=[]
    bo=[]
    for r in range(1,4):
        row=[]
        for c in range(1,4):
            row.append(sq[r*9+c].get())
        bo.append(row)    
    return bo 
            
#mainprogramm
main = tk.Tk()
main.geometry("500x540")
main.resizable(width=0, height=0)
l=tk.Label(main, text="Sudoku Bruteforce Solver")
l["font"]="Arial"
l.place(x=150,y=0)
button1=tk.Button(main, text="quit", command = quit_frame)
button1.place(x=50,y=450)
button2=tk.Button(main, text="readin", command = readin)
button2.place(x=150,y=450)
SquareCreate()

main.mainloop()
Sirius3
User
Beiträge: 18289
Registriert: Sonntag 21. Oktober 2012, 17:20

Entry und Tk werden importiert, aber gar nicht genutzt. Ebenso solltest Du IntVar auch über tk.IntVar ansprechen. Funktionen schreibt man genauso wie Variablennamen komplett klein. Einbuchstabe Variablennamen sind schlecht, place sollte man nicht benutzen, sondern hier wohl grid.
Das IntVar-Objekt, das Du mit dem Entry benutzt, ist ein anderes, als das, dass Du in sq steckst.
Statt über einen Index solltest Du direkt über die Elemente der Liste sq iterieren.
Habe ich schon geschrieben, dass kryptische Variablennamen schlecht sind, weil man rätseln muß, was sq oder bo denn heißen soll?
Rückgabewerte von Event-Handlern werden ignoriert; wer soll denn etwas mit bo anfangen?
kwon
User
Beiträge: 43
Registriert: Samstag 2. Mai 2020, 11:48

Hallo Sirius3,
danke für die Antwort!!
Ich habe das mit place/grid bereinigt.

Könntest du bitte "Das IntVar-Objekt, das Du mit dem Entry benutzt, ist ein anderes, als das, dass Du in sq steckst." am Beispiel erklären? Ich verstehe nicht wie das gemeint ist...
Mir ist das mit "IntVar" noch nicht klar:
In function "squarecreate" beinhalten die entries die IntVar-Werte, die Liste ListeIntVar wird damit gefüllt.

"Statt über einen Index solltest Du direkt über die Elemente der Liste sq iterieren." ---> z.B. for i in ListeIntVar ?

Ich bin ein Anfänger in Python, habe aber schon Programmiererfahrung.
Ich habe einen Code, der in Python eine Liste bestehend aus Listen erwartet.
Ich möchte dafür eine GUI "basteln" (mit Unterstützung natürlich).
Das ist der Code:

Code: Alles auswählen

board = [
    [7,8,0,4,0,0,1,2,0],
    [6,0,0,0,7,5,0,0,9],
    [0,0,0,6,0,1,0,7,8],
    [0,0,7,0,4,0,2,6,0],
    [0,0,1,0,5,0,9,3,0],
    [9,0,4,0,6,0,0,0,5],
    [0,7,0,3,0,0,0,1,2],
    [1,2,0,0,0,7,4,0,0],
    [0,4,9,2,0,6,0,0,7]
]

def solve(bo):
    find = find_empty(bo)
    if not find:
        return True
    else:
        row, col = find

    for i in range(1,10):
        if valid(bo, i, (row, col)):
            bo[row][col] = i

            if solve(bo):
                return True

            bo[row][col] = 0

    return False

def valid(bo, num, pos):
    # Check row
    for i in range(len(bo[0])):
        if bo[pos[0]][i] == num and pos[1] != i:
            return False

    # Check column
    for i in range(len(bo)):
        if bo[i][pos[1]] == num and pos[0] != i:
            return False

    # Check box
    box_x = pos[1] // 3
    box_y = pos[0] // 3

    for i in range(box_y*3, box_y*3 + 3):
        for j in range(box_x * 3, box_x*3 + 3):
            if bo[i][j] == num and (i,j) != pos:
                return False

    return True

def print_board(bo):
    for i in range(len(bo)):
        if i % 3 == 0 and i != 0:
            print("- - - - - - - - - - - - - ")

        for j in range(len(bo[0])):
            if j % 3 == 0 and j != 0:
                print(" | ", end="")

            if j == 8:
                print(bo[i][j])
            else:
                print(str(bo[i][j]) + " ", end="")

def find_empty(bo):
    for i in range(len(bo)):
        for j in range(len(bo[0])):
            if bo[i][j] == 0:
                return (i, j)  # row, col

    return None

print_board(board)
solve(board)
print("___________________")
print_board(board)
wobei man def print_board(bo): evtl. nicht braucht, dafür soll ja die GUI sein.

Das ist der Versuch der neuen GUI:

Code: Alles auswählen

import tkinter as tk
from tkinter import Entry, IntVar

def quit_frame():
    main.destroy()

def squarecreate(): 
    ListeIntVar=[]
    for j in range(1,4):
        for i in range(1,4):
            data = tk.IntVar()
            t = tk.Entry(main, textvariable=data, justify="center",font=("Arial",16))
            t.grid(row=i, column=j)
            t.delete(0)
            ListeIntVar.append(data)
    return  ListeIntVar

def readin():
    bo=[]
    for r in range(1,4):
        row=[]
        for c in range(1,4):
            row.append(ListeIntVar[r*9+c].get())
        bo.append(row)    
    return bo 
            
#mainprogramm
main = tk.Tk()
main.geometry("500x500")
main.resizable(width=0, height=0)
l=tk.Label(main, text="Sudoku Bruteforce Solver")
l["font"]="Arial"
l.grid(row=0, column=1)
button1=tk.Button(main, text="quit", command = quit_frame)
button1.grid(row=4, column=0)
button2=tk.Button(main, text="readin", command = readin)
button2.grid(row=4, column=2)
squarecreate()
main.mainloop()
Ich hoffe sehr, dass mir jemand etwas helfen kann...
Vielen Dank dafür im Voraus!!

kwon
Benutzeravatar
__blackjack__
User
Beiträge: 14085
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@kwon: Anmerkungen zum ersten Modul:

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Und die sollte nur ausgeführt werden wenn das Modul als Programm ausgeführt wird, aber nicht wenn man es nur importiert. Der Grund dafür dürfte eigentlich klar sein, insbesondere bei diesem Modul: Nur dann kann man das von einem anderen Modul aus importieren und den Inhalt von dort aus verwenden.

Namen sollten nicht kryptisch abekürzt werden. Bo ist der Name von einem Schaf auf Orson's Farm, aber kein angemessener Name für ein `board`. `num`, `pos`, `col` sollten wohl `number`, `position`, und `column` heissen.

Wobei `row` und `column` auch nicht ganz passen, denn die enthalten ja keine Reihe oder Spalte, sondern einen Index von einer Reihe oder Spalte. Das mag auf den ersten Blick wie Haarspalterei aussehen, aber der Code ist an der Stelle auch ein bisschen „unpythonisch“, weil der an Stellen mit Indexwerten hantiert wo man das normalerweise in Python nicht machen würde. Man kann *direkt* über die Werte einer Liste iterieren, ohne den Umweg über einen Laufindex. Falls man den *zusätzlich* zum Wert benötigt, gibt es die `enumerate()`-Funktion. Und wenn man über die Elemente von `board` iteriert, dann wäre für diese Listen der Name `row` sehr passend. Da wäre es dann verwirrend wenn man an einer Stelle im Programm den Namen `row` für eine Liste verwendet, die die tatsächlichen Zeilenwerte repräsentiert, und an einer anderen Stelle für den Index einer solchen Zeilenliste.

`str()` und ``+`` um Werte und Zeichenketten zusammenzustückeln ist eher BASIC als Python. Python hat dafür Zeichenkettenformatierung mit der `format()`-Methode auf Zeichenketten, und ab Python 3.6 f-Zeichenkettenliterale.

Die `print_board()`-Funktion sähe „pythonischer“ so aus:

Code: Alles auswählen

def print_board(board):
    for row_index, row in enumerate(board):
        if row_index % 3 == 0 and row_index != 0:
            print("- " * (len(row) + 2))

        for column_index, cell_value in enumerate(row):
            if column_index % 3 == 0 and column_index != 0:
                print("| ", end="")
            print(f"{cell_value} ", end="")
        print()
Ich würde da mit dem eleminieren von Indexwerten aber noch weiter gehen und das externe `more_itertools`-Modul bemühen.
Mit `itertools.intersperse()` lässt sich das auf zwei einzeilige Funktionen völlig ohne Indexwerte eindampfen:

Code: Alles auswählen

from more_itertools import intersperse


def format_row(row):
    return " ".join(intersperse("|", map(str, row), 3))


def print_board(board):
    print("\n".join(intersperse("- " * 11, map(format_row, board), 3)))
`valid()` ist kein guter Funktionsname, weil der keine Tätigkeit beschreibt. Wobei Prädikatsfunktionen, also solche die etwas prüfen und einen Wahrheitswert liefern, oft nach der Eigenschaft benannt werden die sie prüfen, also hier beispielsweise `is_valid_position()`, weil sich das bei einem ``if`` flüssig lesen lässt: ``if is_valid_position(…):``.

Die Funktion ist komplizierter als sie sein müsste, denn sie wird nur aufgerufen wenn klar ist, dass an der angegebenen Position keine Zahl steht. Also eine 0 und das `num` auf jeden Fall ≠0 ist, also ist der jeweilige ``and``-Teil von den ``if``-Bedingungen trivialerweise *immer wahr*, muss also nicht geprüft werden, womit zumindest beim Prüfen von Zeile und Spalte kein Index `i` mehr benötigt wird.

Man kann die drei Tests auch so schreiben, dass die Struktur sehr ähnlich ist, so dass man den Code der sich jeweils unterscheidet in eigene Funktionen herausziehen kann. Mit Funktionsnamen, die die drei Kommentare in der Funktion überflüssig machen, weil man das dann schon am Code ablesen kann.

`find` ist kein guter Name für eine Position.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
from itertools import chain

from more_itertools import intersperse


def format_row(row):
    return " ".join(intersperse("|", map(str, row), 3))


def print_board(board):
    print("\n".join(intersperse("- " * 11, map(format_row, board), 3)))


def find_empty_position(board):
    for row_index, row in enumerate(board):
        for column_index, cell in enumerate(row):
            if cell == 0:
                return (row_index, column_index)
    return None


def iter_row(board, index):
    return iter(board[index])


def iter_column(board, index):
    return (row[index] for row in board)


def iter_box(board, box_position):
    row_offset, column_offset = (coordinate * 3 for coordinate in box_position)
    return (
        board[row_index][column_index]
        for row_index in range(row_offset, row_offset + 3)
        for column_index in range(column_offset, column_offset + 3)
    )


def is_valid_position(board, number, position):
    row_index, column_index = position
    return board[row_index][column_index] == 0 and number not in chain(
        iter_row(board, row_index),
        iter_column(board, column_index),
        iter_box(board, (row_index // 3, column_index // 3)),
    )


def solve(board):
    empty_position = find_empty_position(board)
    if not empty_position:
        return True

    row_index, column_index = empty_position
    for number in range(1, 10):
        if is_valid_position(board, number, (row_index, column_index)):
            board[row_index][column_index] = number
            if solve(board):
                return True
            board[row_index][column_index] = 0

    return False


def main():
    board = [
        [7, 8, 0, 4, 0, 0, 1, 2, 0],
        [6, 0, 0, 0, 7, 5, 0, 0, 9],
        [0, 0, 0, 6, 0, 1, 0, 7, 8],
        [0, 0, 7, 0, 4, 0, 2, 6, 0],
        [0, 0, 1, 0, 5, 0, 9, 3, 0],
        [9, 0, 4, 0, 6, 0, 0, 0, 5],
        [0, 7, 0, 3, 0, 0, 0, 1, 2],
        [1, 2, 0, 0, 0, 7, 4, 0, 0],
        [0, 4, 9, 2, 0, 6, 0, 0, 7],
    ]
    print_board(board)
    solve(board)
    print("_" * 20)
    print_board(board)


if __name__ == "__main__":
    main()
Ziemlich viele Funktionen bekommen das Board als erstes Argument und operieren darauf. Man könnte da sinnvoll eine Klasse draus machen.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
kwon
User
Beiträge: 43
Registriert: Samstag 2. Mai 2020, 11:48

Hallo _blackjack_,
Danke für die ausführliche Antwort!
Ich habe mir ein Buch über Python bestellt.
Anhand der Ausführung sehe ich erneut, dass ich ein ziemlicher Anfänger bin. Ich werde das Projekt hier abbrechen und mich noch weiter einlesen. Das ist wohl am sinnvollsten. Ich werde mich irgendwann später mal wieder melden und das Projekt dann wieder aufgreifen. Vielen Dank für die Unterstützung soweit!!
Viele Grüße, kwon
Benutzeravatar
__blackjack__
User
Beiträge: 14085
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Das `board` als Klasse:

Code: Alles auswählen

#!/usr/bin/env python3
from itertools import chain

from more_itertools import intersperse


class Board:
    EMPTY = 0

    def __init__(self, rows):
        self._rows = rows

    def __str__(self):
        return "\n".join(
            intersperse("- " * 11, map(self._format_row, self._rows), 3)
        )

    def __getitem__(self, position):
        row_index, column_index = position
        return self._rows[row_index][column_index]

    def __setitem__(self, position, number):
        row_index, column_index = position
        self._rows[row_index][column_index] = number

    @staticmethod
    def _format_row(row):
        return " ".join(intersperse("|", map(str, row), 3))

    def find_empty_position(self):
        for row_index, row in enumerate(self._rows):
            for column_index, cell in enumerate(row):
                if cell == self.EMPTY:
                    return (row_index, column_index)
        return None

    def iter_row(self, index):
        return iter(self._rows[index])

    def iter_column(self, index):
        return (row[index] for row in self._rows)

    def iter_box(self, box_position):
        row_offset, column_offset = (
            coordinate * 3 for coordinate in box_position
        )
        return (
            self[row_index, column_index]
            for row_index in range(row_offset, row_offset + 3)
            for column_index in range(column_offset, column_offset + 3)
        )

    def is_valid_position(self, number, position):
        row_index, column_index = position
        return self[position] == self.EMPTY and number not in chain(
            self.iter_row(row_index),
            self.iter_column(column_index),
            self.iter_box((row_index // 3, column_index // 3)),
        )

    def solve(self):
        empty_position = self.find_empty_position()
        if not empty_position:
            return True

        for number in range(1, 10):
            if self.is_valid_position(number, empty_position):
                self[empty_position] = number
                if self.solve():
                    return True
                self[empty_position] = self.EMPTY

        return False


def main():
    board = Board(
        [
            [7, 8, 0, 4, 0, 0, 1, 2, 0],
            [6, 0, 0, 0, 7, 5, 0, 0, 9],
            [0, 0, 0, 6, 0, 1, 0, 7, 8],
            [0, 0, 7, 0, 4, 0, 2, 6, 0],
            [0, 0, 1, 0, 5, 0, 9, 3, 0],
            [9, 0, 4, 0, 6, 0, 0, 0, 5],
            [0, 7, 0, 3, 0, 0, 0, 1, 2],
            [1, 2, 0, 0, 0, 7, 4, 0, 0],
            [0, 4, 9, 2, 0, 6, 0, 0, 7],
        ]
    )
    print(board)
    if board.solve():
        print("_" * 20)
        print(board)
    else:
        print("Can't be solved!")


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
kwon
User
Beiträge: 43
Registriert: Samstag 2. Mai 2020, 11:48

Hallo _blackjack _, vielen Dank für deine sehr guten Ausführungen!!
Ich habe mir den Code und die Erläuterungen auf meinem PC abgelegt und werde darauf zugreifen wenn ich das Buch gelesen habe. Ich finde es ganz schön lobenswert was für eine Mühe du dir mit deinen sehr guten Antworten machst!! Da kann man eine Menge lernen.
Vielen Dank dafür...
Viele Grüße, kwon
Antworten