Az előző, „Sokszögek csúcspontjainak sorbarendezése a helyes megjelenítéshez” című bejegyzésben tárgyalt, a sokszögek csúcspontjait sorbarendező függvényt nem csak a sokszög megfelelő kirajzoltatásához lehet használni, hanem például olyan ellenőrzőfüggvények készítéséhez is, amelyekkel el lehet dönteni, hogy adott csúcspontok milyen fajta négyszöget (trapéz, deltoid, paralelogramma, rombusz, téglalap, négyzet) határoznak meg.
Az ellenőrzéshez az egyes négyszögek geometriából ismert definícióját, illetve valamilyen csak arra jellemző tulajdonságát használjuk. Ezek több esetben is igénylik az oldalhosszak vagy a csúcspontok középpontjának meghatározását, valamint ezek mindegyike egyenlőségének ellenőrzését. Ezért e részfeladatok elvégzéséhez segédfüggvényeket definiálunk a csúcspontok megfelelő sorba rendezését végző függvény mellett. E definícióikat mutatjuk alább:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# Python 3.12+ from itertools import pairwise, batched from statistics import mean from math import isclose, dist, atan2 from typing import Iterable import tkinter as tk def _flatten_xycoords(coords: Iterable) -> Iterable[int | float]: """Az x és y koordinátákat adja vissza egymás után függetlenül attól, hogy azokat az argumentum közvetlenül szolgáltatja, vagy iterálható objektumból származnak. Pl. coords elemei x1, y1, x2, y2 -> kimenet: x1, y1, x2, y2 coords elemei (x1, y1), [x2, y2] -> kimenet: x1, y1, x2, y2 """ for coord in coords: try: iter(coords) if isinstance(coord, (str, bytes, bytearray)): raise TypeError yield from _flatten_xycoords(coord) except TypeError: if isinstance(coord, (int, float)): yield coord else: raise TypeError('A koordináták csak valós számok lehetnek.') def vertices_centroid(*vertices: Iterable) -> tuple[int | float, int | float]: """A megadott csúcspontok középpontját (súlypontját) adja vissza.""" xy_coordinates = list(_flatten_xycoords(vertices)) # A kapott lista: [x1, y1, x2, y2, ..., xn, yn] # Kinyerjük az x koordináták sorozatát és az y koordináták sorozatát. x_coords, y_coords = xy_coordinates[::2], xy_coordinates[1::2] # Meghatározzuk a csúcspontok középpontját (súlypontját), ami mint az a geometriából ismert, a csúcspontok számtani közepe. center_x, center_y = mean(x_coords), mean(y_coords) return center_x, center_y def sort_vertices_for_proper_plotting(*vertices: Iterable) -> tuple: """Bármilyen sorrendben vannak a kirajzolandó sokszög csúcspontjai megadva, a visszatérési érték a csúcspontok olyan sorozata lesz, amellyel a sokszög megfelelően, azaz keresztező vonalak nélkül lesz megjelenítve. A csúcspontokat az x, y koordináták egymást követő felsorolsával, vagy az x, y párokat szolgáltató iterálható objektumok felsorolásával lehet megadni. """ # Meghatározzuk a csúcspontok középpontját (súlypontját) center_x, center_y = vertices_centroid(vertices) xy_coordinates = list(_flatten_xycoords(vertices)) # A kapott lista: [x1, y1, x2, y2, ..., xn, yn] # Kiszámítjuk az egyes csúcspontok középponttól vett szögét. vertices_angles = {(x, y): atan2(x - center_x, y - center_y) for x, y in batched(xy_coordinates, 2)} # A csúcspontokat a szögeik szerint rendezzük. sorted_vertices_angles = sorted(vertices_angles.items(), key=lambda t: t[1]) # A rendezett csúcspontokkal térünk vissza. return tuple(point for point, angle in sorted_vertices_angles) def all_equal(*numbers: int | float, relative_tolerance=1e-9) -> bool: """True értéket ad vissza, ha az összes argumentum egyenlő az adott relatív tolerancián belül.""" # Azt, hogy minden érték egyenlő, úgy határozzuk meg, hogy kihasználjuk az egyenlőségi reláció # tranzitivitását: ha n1==n2 és n1==n3, akkor ebből következik, hogy n2==n3. return all(isclose(numbers[0], number, rel_tol=relative_tolerance) for number in numbers[1:]) def side_lengths(*vertices: Iterable) -> list[float]: sorted_points = sort_vertices_for_proper_plotting(vertices) # Ahhoz, hogy a sokszög csúcspontpárokból számolt oldalhosszai között a sorban utolsó oldal is # szerepeljen, az első csúcspontot a sorozat végén, utolsóként fel kell venni. return [dist(p1, p2) for p1, p2 in pairwise([*sorted_points, sorted_points[0]])] |
Az egyes négyszögfajtákra vonatkozó ellenőrző függvények pedig a következők:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
def is_kite(*vertices: Iterable) -> bool: """Akkor ad vissza True értéket, ha a megadott pontok egy deltoidot alkotnak. Egy sokszög deltoid, ha négyszög és két-két szomszédos oldala egyenlő hosszú. A csúcspontokat az x, y koordináták egymást követő felsorolsával, vagy az x, y párokat szolgáltató iterálható objektumok felsorolásával lehet megadni. """ sides = side_lengths(vertices) # Ha két-két szomszédos oldal egyenlő, akkor deltoid. Ez a rendezett csúcspontokból meghatározott oldalak sorozatában # úgy fordulhat elő, hogy vagy az első kettő és a második kettő oldal egyenlő, vagy a középső kettő és a két szélső. return len(sides) == 4 and \ (all_equal(*sides[:2]) and all_equal(*sides[2:])) or (all_equal(*sides[1:3]) and all_equal(sides[0], sides[-1])) def is_trapezoid(*vertices: Iterable) -> bool: """Akkor ad vissza True értéket, ha a megadott pontok egy trapézt alkotnak. Egy sokszög trapéz, ha négyszög és vannak párhuzamos szemközti oldalai. A csúcspontokat az x, y koordináták egymást követő felsorolsával, vagy az x, y párokat szolgáltató iterálható objektumok felsorolásával lehet megadni. """ sorted_points = sort_vertices_for_proper_plotting(vertices) if len(sorted_points) == 4: x1, y1, x2, y2, x3, y3, x4, y4 = _flatten_xycoords(sorted_points) # A csúcspontok rendezettségéből adódóan, ha a négyszög trapéz, akkor két eset lehet: # 1) az (x1, y1) és (x2, y2) pontokkal meghatározott oldal, valamint az (x3, y3) és (x4, y4) pontokkal # meghatározott oldal szemközti oldalak, és ezek párhuzamosak, vagy # 2) az (x2, y2) és (x3, y3) pontokkal meghatározott oldal, valamint az (x4, y4) és (x1, y1) pontokkal # meghatározott oldal szemközti oldalak, és ezek párhuzamosak. # A párhuzamosságot a szemközti oldalak meredekségének összevetésével ellenőrizzük. if isclose((y2 - y1) / (x2 - x1), (y4 - y3) / (x4 - x3)) or isclose((y3 - y2) / (x3 - x2), (y4 - y1) / (x4 - x1)): return True return False def is_parallelogram(*vertices: Iterable) -> bool: """Akkor ad vissza True értéket, ha a megadott pontok egy paralelogrammát alkotnak. Egy sokszög paralelogramma, ha négyszög és szemközti oldalai párhuzamosak. A paralelogramma szemközti oldalai egyenlő hosszúak. A csúcspontokat az x, y koordináták egymást követő felsorolsával, vagy az x, y párokat szolgáltató iterálható objektumok felsorolásával lehet megadni. """ sorted_points = sort_vertices_for_proper_plotting(vertices) if len(sorted_points) == 4: x1, y1, x2, y2, x3, y3, x4, y4 = _flatten_xycoords(sorted_points) # A csúcspontok rendezettségéből adódóan, ha a négyszög paralelogramma, akkor két eset lehet: # 1) az (x1, y1) és (x2, y2) pontokkal meghatározott oldal, valamint az (x3, y3) és (x4, y4) pontokkal # meghatározott oldal szemközti oldalak, és ezek párhuzamosak, vagy # 2) az (x2, y2) és (x3, y3) pontokkal meghatározott oldal, valamint az (x4, y4) és (x1, y1) pontokkal # meghatározott oldal szemközti oldalak, és ezek párhuzamosak. # A párhuzamosságot a szemközti oldalak meredekségének összevetésével ellenőrizzük. # Párhuzamosság esetén a szakaszhosszok egyenlőségét is ellenőrizni kell. if isclose((y2 - y1) / (x2 - x1), (y4 - y3) / (x4 - x3)): return isclose(dist((x1, y1), (x2, y2)), dist((x3, y3), (x4, y4))) elif isclose((y3 - y2) / (x3 - x2), (y4 - y1) / (x4 - x1)): return isclose(dist((x2, y2), (x3, y3)), dist((x4, y4), (x1, y1))) return False def is_rhombus(*vertices: Iterable) -> bool: """Akkor ad vissza True értéket, ha a megadott pontok egy rombuszt alkotnak. Egy sokszög rombusz, ha négyszög és minden oldala egyenlő. A csúcspontokat az x, y koordináták egymást követő felsorolsával, vagy az x, y párokat szolgáltató iterálható objektumok felsorolásával lehet megadni. """ sides = side_lengths(vertices) # Ha négyszög (négy oldala van) és minden oldala egyenlő, akkor rombusz. return len(sides) == 4 and all_equal(*sides) def is_rectangle(*vertices: Iterable) -> bool: """Akkor ad vissza True értéket, ha a megadott pontok egy téglalapot alkotnak. Egy sokszög téglalap, ha négyszög és minden szöge egyenlő. Ebből következik, hogy a csúcsok középponttól (sűlyponttól) mért távolsága egyenlő. A csúcspontokat az x, y koordináták egymást követő felsorolsával, vagy az x, y párokat szolgáltató iterálható objektumok felsorolásával lehet megadni. """ # Az ellenörzés módja: négy csúcs van-e, és a csúcsok középponttól (súlyponttól) mért távolsága egyenlő-e. sorted_points = sort_vertices_for_proper_plotting(vertices) # Meghatározzuk a csúcspontok középpontját (súlypontját). centroid_x, centroid_y = vertices_centroid(vertices) # Ha négyszög és a csúcsok távolsága a középpontól egyenlő, akkor téglalap. distances_to_vertices = (dist((centroid_x, centroid_y), vertex) for vertex in sorted_points) return len(sorted_points) == 4 and all_equal(*distances_to_vertices) def is_square(*vertices: Iterable) -> bool: """Akkor ad vissza True értéket, ha a megadott pontok egy négyzetet alkotnak. Egy sokszög négyzet, ha négyszög és minden szöge egyenlő és minden oldala egyenlő. Ebből következik, hogy a csúcsok középponttól (sűlyponttól) mért távolsága egyenlő. A csúcspontokat az x, y koordináták egymást követő felsorolsával, vagy az x, y párokat szolgáltató iterálható objektumok felsorolásával lehet megadni. """ sorted_points = sort_vertices_for_proper_plotting(vertices) sides = side_lengths(vertices) # Meghatározzuk a csúcspontok középpontját (súlypontját). centroid_x, centroid_y = vertices_centroid(vertices) # Ha négyszög és a csúcsok távolsága a középpontól egyenlő, akkor téglalap. distances_to_vertices = (dist((centroid_x, centroid_y), vertex) for vertex in sorted_points) # Ha téglalap és minden oldala egyenlő, akkor négyzet. return all_equal(*distances_to_vertices) and all_equal(*sides) |
A részletes kommentek segítik a függvények működésének megértését.
Az ellenőrző függvények működésének tesztelését végző alábbi programkód grafikus felhasználói felületen jeleníti meg a csúcspontokkal megadott négyszögeket, és alatta kiírja, hogy az adott négyszög milyen speciális fajtának felel meg. A tesztkód nem bonyolult, és a kommentek itt is könnyítik a megértést.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
# TESZT # Különböző négyszögek csúcspontjai. quads_vertices = [[(100, 0), (40, 20), (180, 60), (100, 160)], [(100, 0), (50, 40), (150, 40), (100, 160)], [(0, 0), (170, 0), (50, 160), (150, 160)], [(0, 0), (100, 0), (50, 160), (150, 160)], [(100, 0), (50, 80), (150, 80), (100, 160)], [(100, 0), (50, 17.550020016016017), (150, 142.44997998398398), (100, 160)], [(100, 0), (20, 80), (180, 80), (100, 160)], ] # A négyszögek megjelenítési színei. colors = dict(zip(range(7), ['silver', 'orange', 'cyan', 'black', 'blue', 'red', 'green'])) # A vizsgált négyszögtípusok és azokhoz tartozó ellenőrző függvények sorozatai. quad_types = ['Trapéz', 'Paralelogramma', 'Rombusz', 'Téglalap', 'Négyzet', 'Deltoid'] check_funcs = [is_trapezoid, is_parallelogram, is_rhombus, is_rectangle, is_square, is_kite] root = tk.Tk() root.title('Négyszögfajták') tk.Label(root, text='négyszögek'.upper(), font=('Arial', 20, 'bold')).grid(row=0, column=0) for ci, vertices in enumerate(quads_vertices): # Az egyes négyszögeket egymás mellé rendezett elkülönült vászon elemeken jelenítjük meg. canvas = tk.Canvas(root, bg='light yellow', width=200, height=170) canvas.create_polygon(*sort_vertices_for_proper_plotting(vertices), fill=colors[ci]) canvas.grid(row=0, column=ci + 1) # Megvizsgáljuk, hogy az adott csúcspontsorozat milyen négyszögfajta lehet, és ezt # táblázatos elrendezésben foglaljuk össze. for ri, type_func in enumerate(zip(quad_types, check_funcs)): quad_type, check_func = type_func lbl = tk.Label(root, text='igen' if (b := check_func(*vertices)) else 'nem', font=('Source Code Pro', 20, ('bold' if b else 'normal'))) lbl.grid(row=ri + 1, column=ci + 1, sticky='news') lbl = tk.Label(root, text=quad_type, font=('Consolas', 20)) lbl.grid(row=ri + 1, column=0, sticky='e') root.mainloop() |
A tesztprogram futtatása után a következő eredménykép jelenik meg:

E bejegyzés témájához a Python tudásépítés lépésről lépésre című e-könyvben leginkább a „Műveletek”, az „Egymáshoz rendelve – függvények”, a „Beépített függvények”, és a „Grafikus felhasználói felület készítése” fejezet kapcsolódik.