Jag har valt uppgiften 127 Platsbokning på SJ
- Programmet startas och ev. lagrade bokningar på tåg laddas in.
- Om det finns lagrade bokningar, verifiera att dessa är aktuella och radera ev. gamla bokningar.
- Om inget finns lagrat laddas någon default-uppsättning på tåg som användaren ombeds spara.
- Användaren får välja om de vill:
- (a) Boka biljett(er)
- (b) Avboka biljett
- (c) Visa bokningar
- (d) Avsluta
- Korresponderande flöde påbörjas. Se nedan.
- När flödet avslutas får användaren alternativen i 2. igen.
- När avsluta välja i 2. kommer alla lagrade bokningar att skrivas till en körningsfil för användning nästa gång och bokade biljetter skrivs ut till plats önskad av användaren i pop-up.
- Rätt avgång väljs.
- Användaren för välja ifall de vill boka 1 eller flera platser alternativt bekräfta bokningen och avsluta.
- Ifall 1 plats väljs, bokas angiven passagerare på platsen ifall den är tom, annars visasa felmedellande.
- Ifall fler platser väljs, försöker systemet boka alla platser intill varandra. Ifall detta inte går frågas användaren ifall skiljda platser önskas. Om ja: boka resande. Om nej: gå vidare.
- Flödet upprepas från 1. tills användaren väljer att avsluta.
- Användaren anger platsnr. eller passagerarnamn till önskad avbokning.
- Passageraren avbokas ifall denne finns, annars visas felmedellande.
- Tillbaka till Huvudmeny.
- Befintliga biljetter i denna app-instans skrivs ut.
- Tillbaka till huvudmenyn.
Notera: Jag har valt att wrap:a hela appen i klassen App och generellt arbetat väldigt objekt-orienterat. Därför ligger det mesta av logiken i klasserna.
load_trains_from_file(path: str) -> list[Train]
- Laddar in tåg från angivna filen och returnerar de som en lista. Använderpickle
för att undvika komplex textbaserad serialisering.save_trains_to_file(trains: Iterable[Train], path: str) -> None
- Skriver ut alla tåg binärt viapickle
till den angivna filen.add_train_to_file(train: Train) -> None
- Lägger till den angivna tåget i den angivna filen. Detta är en hjälpfunktion för att fylla tågfilen när jag först skapar den.
Innehåller information om en stol, dess position och om den är upptagen. Är praktiskt taget endast en struct med lite hjälpmetoder
number: int
- stolsnummer i vagnenpassenger_name: str
- passagerare som sitter i stolen
is_booked() -> bool
- Ange om stolen är bokad
Lagrar en vagn, dess stolar och alla passagerare.
seats: list[tuple[list[Seat], list[Seat]]]
- lista med alla par av stolsrader separerade av en gång. Varje element innehåller en tuple av två listor med stolar, en för varje sida av gången.seating_configuration: str
- sittkonfiguration (ex. "2+2", "3+2" etc.)number: int
- vagnnummer
-
get_seat_num(number: int) -> Seat
- returnerar en specifik stol baserat på nummer. GerIndexError
om angivet stolsnummer inte finns i vagnen.För ett stolsnummer
$n$ , i en vagn med$n_l$ stolar till vänster om gånger och$n_r$ stolar till höger, låt$\displaystyle\left\{\begin{array}{l}b_r = n_l + n_r \\ r = \left\lceil n / b_r \right\rceil \\ i = n - b_r \cdot (r - 1) \end{array}\right.$ där
$r$ är stolens radnummer och$i$ är stolens index i raden. Detta innebär att man kommer åt stol nr$n$ med uttrycketCarriage.seats[r - 1][0][i - 1]
om den befinner sig på vänster sida om gånger, dvs$i \leq n_l$ annars med uttryckerCarriage.seats[r - 1][1][i - 1 - n_l]
. -
get_seat_name(passenger_name: str) -> Seat
- Returnerar den stol med den angivna passageraren. GerValueError
om fler än en stol med samma namn finns. GerKeyError
om passageraren inte kan hittas. -
book_passenger(name: str, seat_num: int) -> None
- Bokar in passageraren i en specifik stol. GerIndexError
ifall ogiltigt stolsnummer anges. GerValueError
ifall stolen redan är bokad. -
unbook_seat(number: int) -> None
- Tar bort passagerare från den angivna stolen.IndexError
ifall stolen inte finns. -
unbook_passenger(passenger_name: str) -> None
- Tar bort angiven passagerare från angiven stol.KeyError
om ingen sådan passagerare finns.
Lagrar ett tåg, dess avgångstid, ankomsttid, startstation, destination och en lista med vagnar.
train_number: int
- unikt tågnummerstart: str
- startstationdestination: str
- destinationdeparture_time: datetime
- avgångstidarrival_time: datetime
- ankomsttidcarriages: list[Carriage]
- lista med vagnar
book_passenger(passenger_name: str, carriage_number: int, seat_number: int) -> None
- Bokar in en passagerare på angiven plats. GerIndexError
om platsen inte finns ochValueError
ifall stolen redan är bokad.unbook_seat(carriage_number: int, seat_number: int) -> None
- Tar bort passagerare från den angivna stolen.IndexError
ifall stolen inte finns.unbook_passenger(passenger_name: str) -> None
- Tar bort angiven passagerare från angiven stol.KeyError
om ingen sådan passagerare finns.__lt__(self, other: Train) -> bool
- Jämförelse för sortering. Utgår fråndeparture_time
Ärver tkinter.Tk
. Innehåller all kod för att generera GUI:n samt all state för den nuvarande körningen. Den innehåller även kod för att interagera med användaren. Många widgets kommer från separata klasser, se rubriken nedan.
_trains: list[Train]
- Intern lista av alla tåg. Sorteras utefter avgångstid enligt (se Train.__lt__()). Används för att lagra alla giltiga avgångar.
En mängd olika små klasser för att göra GUI:n mer modulär. Ett exempel på mitt allra vanligaste som säkert kommer vara med när skriver GUI:n:
class LabeledEntry(ttk.Frame):
"""An entry with a corresponding label which shows what should be entered
"""
def __init__(self, master, label_text: str, entry_var: tk.StringVar *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.rowconfigure(1, weight=1)
self.label = ttk.Label(self, text=label_text)
self.label.grid(column=0, row=0, sticky="w")
self.entry = ttk.Entry(self, textvariable=entry_var)
self.entry.grid(column=0, row=1, sticky="nesw", padding=5)
Detta är dår för att jag ska kunna skriva som nedan och inte behöva upprepa bägge widgets varje gång.
class App(tk.Tk)
...
def __init__(...):
self.some_input = LabeledEntry(self.some_frame, "foo", bar)
self.some_input.grid(column=0, row=0, sticky="nesw")
Jag har type-hintat de flesta attribut under rubriken Klasser men kommer med tanke på bedömningskriterierna sammanfatta dem här.
Tågvagnens stolar representeras som en list[tuple[list[Seat], list[Seat]]]
. Yttersta listan indexeras på tågets rader, inre listan är en tuple som innehåller stolarna till vänster respektive höger om gången på den raden. Tuple är lämpligt då denna alltid kommer innehålla exakt två element. Innersta listorna är den variabla mängden stolar på var sin sida av gången.
Fördelen med denna struktur är att det finns än tydlig avskiljning mellan de olika sidorna av gången. Detta kommer att vara till nytta för rendrering i en GUI till skillnad från exempelvis list[list[Seat]]
där sidorna inte är separerade.
Denna är en struct med ett heltalsnummer och en sträng som passagerarnamn
En lista men vagnar. Ett heltal som tågnummer, datetime
-objekt för att avgångs-/ankomsttider samt strängar för start/destination.
Har en lista med aktuella tåg som kan sorteras på avgångstid.