He estado queriendo hacer un visualizador de algoritmo de clasificación usando python y me decidí a usar la biblioteca Tkinter como mi forma de visualizar los datos (si alguien tiene mejores bibliotecas para usar, estoy abierto a sugerencias, busqué en matplotlib pero me desanimé) . Mi problema es que mientras estoy ordenando la matriz, quiero hacer un intercambio, mostrar la matriz actualizada después del intercambio, luego continuar la clasificación; pero lo que termina sucediendo es que la matriz se ordena y luego se actualiza toda la matriz ordenada.

import tkinter as tk
from tkinter import ttk
import random
import time

class SortingVisualizer(tk.Tk):
    def __init__(self, *args, **kwargs):
        tk.Tk.__init__(self, *args, **kwargs)
        tk.Tk.wm_title(self, "Sorting Visualizer")
        tk.Tk.wm_minsize(self, width=600, height=500)
        tk.Tk.wm_resizable(self, width=False, height=False)


        self.topFrame = tk.Frame(self)
        self.topFrame.grid(row=0, sticky='w')

        self.sortOptions = ['Select Algorithm','Bubble sort','Quicksort', 'Merge sort']
        self.optionVar = tk.StringVar()
        self.optionDrop = ttk.OptionMenu(self.topFrame, self.optionVar, *self.sortOptions)
        self.optionDrop.config(width=15)
        self.optionDrop.grid(row=0, column=1, sticky='ew')

        self.sortButton = ttk.Button(self.topFrame, text = "Sort", command = lambda: bubbleSort(self))
        self.sortButton.grid(row=0, column=2, sticky='w')
        self.genButton = ttk.Button(self.topFrame, text = "Generate New Array", command = self.newArray)
        self.genButton.grid(row=0, column=0)

        self.generateArray()

    def newArray(self):
        self.sortCanvas.destroy()
        self.generateArray()

    def generateArray(self):
        self.array = []
        self.numOperations = 0
        i = 0
        while i < 15:
            height = random.randint(15, 200)
            self.array.append(height)
            i = i + 1
        self.drawCanvas()

    def drawCanvas(self):
        self.sortCanvas = tk.Canvas(self, width=600, height=450)
        self.sortCanvas.grid(row=1)
        self.sortCanvas.create_line(15, 15, 585, 15)
        label = "Number of Operations: " + str(self.numOperations)
        self.numLabel = tk.Label(self.topFrame, text = label)
        self.numLabel.grid(row=1)

        bar_width = 20
        bar_gap = bar_width + 10
        start_x = 30
        start_y = 15
        for bar_height in self.array:
            x1 = start_x + bar_width
            y1 = start_y + bar_height
            self.sortCanvas.create_rectangle(start_x, start_y, x1, y1*2, fill='green')
            start_x = start_x + bar_gap

    def redrawCanvas(self):
        self.sortCanvas.destroy()
        self.drawCanvas()

def bubbleSort(self):
    n = len(self.array)
    for i in range(n):
        for j in range(0, n-i-1):
            if self.array[j]>self.array[j+1]:
                temp = self.array[j]
                self.array[j] = self.array[j+1]
                self.array[j+1] = temp
                self.numOperations += 1
                self.after(300, self.redrawCanvas)

app = SortingVisualizer()
app.mainloop()

También probé app.after (300, self.redrawCanvas) y obtuve el mismo resultado

3
daylenp 9 oct. 2019 a las 10:41

3 respuestas

La mejor respuesta

Hiciste un muy buen primer intento y ya casi estabas allí. He realizado algunos cambios en su código, lo más importante es que introduje un objeto root (tk.Tk()) para poder hacer root.update () para volver a dibujar a sort_canvas en un nuevo método { {X3}}. Para evitar algunos 'parpadeos' en lugar de destruir el lienzo cada vez, es mejor eliminar solo los elementos 'barra'. Además, me tomé la libertad de cambiar algunos de los nombres de las variables para hacerlo un poco más Pythonic (debería usar guiones bajos en lugar de mayúsculas) y agregué la declaración if _name__ == '__main__'.

Echa un vistazo al siguiente código.

import tkinter as tk
from tkinter import ttk
import random

class SortingVisualizer:

    def __init__(self):
        self.root = tk.Tk()
        self.root.wm_title("Sorting Visualizer")
        self.root.wm_minsize(width=600, height=500)
        self.root.wm_resizable(width=False, height=False)

        self.top_frame = tk.Frame(self.root)
        self.top_frame.grid(row=0, sticky='w')

        self.sort_options = ['Select Algorithm', 'Bubble sort', 'Quicksort', 'Merge sort']
        self.option_var = tk.StringVar()
        self.option_drop = ttk.OptionMenu(
            self.top_frame, self.option_var, *self.sort_options)
        self.option_drop.config(width=15)
        self.option_drop.grid(row=0, column=1, sticky='ew')

        self.sort_button = ttk.Button(
            self.top_frame, text="Sort", command=self.bubble_sort)
        self.sort_button.grid(row=0, column=2, sticky='w')

        self.gen_button = ttk.Button(
            self.top_frame, text="Generate New Array", command=self.new_array)
        self.gen_button.grid(row=0, column=0)

        self.sort_canvas = tk.Canvas(self.root)
        self.bars = []

    def new_array(self):
        self.generate_array()
        self.blip_canvas()

    def generate_array(self):
        self.array = []
        self.num_operations = 0
        i = 0
        while i < 15:
            height = random.randint(15, 200)
            self.array.append(height)
            i = i + 1

    def draw_canvas(self):
        label = "Number of Operations: " + str(self.num_operations)
        self.num_label = tk.Label(self.top_frame, text=label)
        self.num_label.grid(row=1)

        self.sort_canvas = tk.Canvas(self.root, width=600, height=450)
        self.sort_canvas.grid(row=1)
        self.sort_canvas.create_line(15, 15, 585, 15)

        bar_width = 20
        bar_gap = bar_width + 10
        start_x = 30
        start_y = 15
        self.bars = []
        for bar_height in self.array:
            x1 = start_x + bar_width
            y1 = start_y + bar_height
            self.bars.append(self.sort_canvas.create_rectangle(
                start_x, start_y, x1, y1*2, fill='green'))
            start_x = start_x + bar_gap

    def blip_canvas(self):
        self.sort_canvas.delete(self.bars)
        self.draw_canvas()
        self.root.update()
        self.root.after(200)

    def bubble_sort(self):
        n = len(self.array)
        for i in range(n):
            for j in range(n-i-1):
                if self.array[j] > self.array[j+1]:
                    self.array[j], self.array[j+1] = self.array[j+1], self.array[j]
                    self.num_operations += 1
                    self.blip_canvas()

    def start(self):
        tk.mainloop()


if __name__ == '__main__':
    app = SortingVisualizer()
    app.start()

Tenga en cuenta en bubble_sort que no necesita la variable temporal para intercambiar los valores de array [j] y array [j + 1]

En lugar de usar time.sleep(0.2) para establecer un retraso, he usado:

self.root.update()
self.root.after(200)

Como se sugiere en Actualizar botón después de demora

También puede apegarse a su código original y simplemente hacer algunos cambios.
1) Cambiar el sortButton

self.sortButton = ttk.Button(self.topFrame, text = "Sort", command=self.bubbleSort)

2) Sangra el método de clasificación de burbujas para alinearlo con el Visualizador de clasificación

3) Cambie el método redrawCanvas a:

    def redrawCanvas(self):
        self.sortCanvas.destroy()
        self.drawCanvas()
        self.update()
        self.after(300)

Y

4) en bubbleSort realice la llamada a redrawCanvas:

        for j in range(0, n-i-1):
            if self.array[j]>self.array[j+1]:
                temp = self.array[j]
                self.array[j] = self.array[j+1]
                self.array[j+1] = temp
                self.numOperations += 1
                self.redrawCanvas()

Et voila, ¡funcionará!

1
Bruno Vermeulen 9 oct. 2019 a las 13:24

Puede Thread su función bubbleSort. También parece tener más sentido que bubbleSort sea un método de la clase:

import threading, time

class SortingVisualizer(tk.Tk):
    def __init__(self, *args, **kwargs):
        ...

        self.sortButton = ttk.Button(self.topFrame, text = "Sort", command = lambda: threading.Thread(target=self.bubbleSort).start())

        ...

    ...


    def bubbleSort(self):
        n = len(self.array)
        for i in range(n):
            for j in range(0, n-i-1):
                if self.array[j]>self.array[j+1]:
                    temp = self.array[j]
                    self.array[j] = self.array[j+1]
                    self.array[j+1] = temp
                    self.numOperations += 1
                    self.redrawCanvas()
                    time.sleep(0.1)
1
Henry Yik 9 oct. 2019 a las 08:09

Solo una solución alternativa además del enhebrado. Solía encontrar un problema que al enhebrar las funciones, pueden acceder a algunos dispositivos externos (creé una GUI para monitorear algún hardware) al mismo tiempo y causar conflictos. Al usar .after (), tkinter manejará el orden de las tareas para evitar conflictos.

Puede redefinir su función bubbleSort para que cada iteración del bucle for se cambie a una recursión llamando nuevamente a la función.

def bubbleSort(self, i = 1, j = 0):
    n = len(self.array)
    if self.array[j]>self.array[j+1]:
        temp = self.array[j]
        self.array[j] = self.array[j+1]
        self.array[j+1] = temp
        self.numOperations += 1
    j += 1
    if j == n-i-1:
        j = 0
        i += 1
    if i < n:
        self.after(1, lambda: self.bubbleSort(i,j))
1
pok fung Chan 9 oct. 2019 a las 08:51
58299276