Zadanie - książka kontaktowa

Zadanie - książka kontaktowa

Kolejnym z naszych zadań było stworzenie swego rodzaju "książki kontaktowej" z użyciem pętli while i możliwością zapisywania danych do pliku. Przyznam, że o ile samo napisanie tej aplikacji szczególnie kłopotliwe nie było to jednak trochę pokonała mnie opcja obsługi plików. Nie rozumieliśmy się z terminalem, ale że jestem uparta chyba udało mi się rozwiązać ten problem (w sensie u mnie działa 😉).

import json
import os

plik = "ksiazka_adresowa.json"

if os.path.exists(plik):
    with open(plik, "r", encoding="utf-8") as f:
        ksiazka = json.load(f)
else:
    ksiazka = {}

print("Witaj w swojej książce kontaktowej.")

while True:
    print("""Co chcesz zrobić?
          1 - dodaj kontakt
          2 - aktualizuj dane kontaktowe
          3 - usuń wpis
          4 - wydruk nazw
          5 - wydruk całej książki z kontaktami
          6 - wyszukaj kontakt
          0 - zapisz i zamknij """)
    
    wybor = input("Wybierz numer operacji: ")

    if wybor == "1": # dodaj kontakt
        imie = input("Podaj nazwę kontaktu: ")
        if imie in ksiazka:
            print(f"Kontakt {imie} już istnieje, dane kontaktowe: {ksiazka[imie]}.\nJeżeli chcesz zmienić wybierz opcję '2'.")
        else:
            numer = input("Podaj dane kontaktowe: ")
            ksiazka[imie] = numer             
            print(f"Kontakt {imie} został dodany.")

    elif wybor == "2": # aktualizuj dane kontaktowe
        imie = input("Podaj nazwę kontaktu który chcesz zmienić: ")
        if imie in ksiazka:
            nowy_numer = input("Podaj nowe dane kontaktowe: ")
            ksiazka[imie] =  nowy_numer
            print("Wpis został zaktualizowany.")
        else:
            print("Nie ma takiego kontaktu.")

    elif wybor == "3": # usuń wpis
        imie = input("Podaj nazwę kontaktu który chcesz usunąć: ")
        if imie in ksiazka:
            del ksiazka[imie]
            print("Kontakt został usunięty.")
        else:
            print("Nie ma takiego kontaktu.")

    elif wybor == "4": # wydruk nazw
        if ksiazka:
            print("Oto Twoje nazwy kontaktów:")
            for imie in sorted(ksiazka):
                print(imie)
        else:
            print("Brak kontaktów.")

    elif wybor == '5': # wydruk całej książki z kontaktami
        if ksiazka:
            print("Oto wszystkie Twoje kontakty:")
            for imie, numer in sorted(ksiazka.items()):
                print(f"Nazwa: {imie} - kontakt: {numer}")
        else:
            print("Brak kontaktów.")

    elif wybor == '6': # szukanie po części nazwy
        wyszukaj = input("Podaj część nazwy kontaktu którego szukasz: ").strip().lower()
        znalezione = {imie: numer for imie, numer in ksiazka.items() if imie.lower().startswith(wyszukaj)}
        if znalezione:
            for imie, numer in znalezione.items():
                print(f"Nazwa: {imie} kontakt: {numer}")
        else:
            print("Nie znaleziono pasujących wyników.")

    elif wybor == '0': #zamknij
        zapis = input("Czy chcesz zapisać zmiany? [t/n]: ").lower()
        if zapis == "t":
            with open(plik, "w", encoding="utf=8") as f:
                json.dump(ksiazka, f, ensure_ascii=False, indent=4, sort_keys=True)
            print("Zmiany zostały zapisane.")
        else:
            print("Zmiany nie zostały zapisane.")
        print("Koniec programu. Do zobaczenia.")
        break
    
    else:
        print("Niepoprawny wybór. Spróbuj jeszcze raz.")

Na początku programu importuje dwie biblioteki json oraz os. Czy biblioteka os jest potrzebna? Niekoniecznie. W tym przypadku wykorzystuje ją do sprawdzenia czy plik do którego zapisywane będą kontakty istnieje i jeżeli nie to zostanie utworzona pusty słownik, stąd deklaracja ksiazka = {}. Zapis do pliku (a w przypadku gdy on nie istnieje to utworzenie go) odbywa się po wybraniu opcji 0 czyli zapisz i zamknij. Utworzyłam też zmienną plik do której przypisałam plik z rozszerzeniem .json żeby łatwiej było mi pisać kod (i przy okazji łatwiej było modyfikować, gdybym np. stwierdziła, że potrzebuję utworzyć nowy plik z inną nazwą). Swoją drogą okazało się, że moim problemem obsługi plików był fakt, że mój plik ksiazka_adresowa.json był pusty... tyle czasu ile spędziłam nad znalezieniem co jest nie tak i czemu nie działa a wystarczyło w pliku dopisać {} 😒

Można to też zrobić inaczej:

try:
     with open(plik, "r", encoding="utf-8") as f:
         ksiazka = json.load(f)
 except FileNotFoundError:
     ksiazka = {}

Instrukcja try służy do obsługi wyjątków (tudzież błędów), które mogą pojawić się w trakcie wykonywania programu. Tutaj chcę otworzyć plik z książką kontaktową, ale zaznaczam w programie, że to może się nie udać (bo np. plik nie istnieje). Jeżeli pliku nie da się otworzyć, czyli wystąpi wyjątek FileNotFoundError program nie przerywa swojego działania tylko tworzy pusty słownik który będzie nam służył do zapisu kontaktów. Chociaż widzę drobny problem, ale póki co go zignoruję 😅

Następnie wchodzimy w pętlę while w której mamy rozwijane menu. Z własnej inicjatywy dopisałam wyszukaj kontakt, ponieważ zaczęłam kombinować z różnymi funkcjami i pomyślałam, że to będzie dobry dodatek. Zatem przechodzę do opisu każdej funkcji!

1 - dodaj kontakt

    if wybor == "1": # dodaj kontakt
        imie = input("Podaj nazwę kontaktu: ")
        if imie in ksiazka:
            print(f"Kontakt {imie} już istnieje, dane kontaktowe: {ksiazka[imie]}.\nJeżeli chcesz zmienić wybierz opcję '2'.")
        else:
            numer = input("Podaj dane kontaktowe: ")
            ksiazka[imie] = numer             
            print(f"Kontakt {imie} został dodany.")

Użytkownik podaje nazwę kontaktu zapisanego pod zmienną imie. Program sprawdza czy taki kontakt już istnieje w słowniku ksiazka. Jeśli tak to informuje o tym użytkownika, jeżeli nie to przechodzimy do dodania danych kontaktowych numer które zostają zapisane w słowniku pod daną nazwą.

2 - aktualizuj dane kontaktowe

    elif wybor == "2": # aktualizuj dane kontaktowe
        imie = input("Podaj nazwę kontaktu który chcesz zmienić: ")
        if imie in ksiazka:
            nowy_numer = input("Podaj nowe dane kontaktowe: ")
            ksiazka[imie] =  nowy_numer
            print("Wpis został zaktualizowany.")
        else:
            print("Nie ma takiego kontaktu.")

Tutaj będziemy nadpisywać dane do danego kontaktu. Czyli użytkownik wpisuje nazwę kontaktu, jeżeli istnieje to przechodzi do wpisania nowych danych kontaktowych. W innym przypadku program informuje, że dany kontakt nie istnieje.

3 - usuń wpis

    elif wybor == "3": # usuń wpis
        imie = input("Podaj nazwę kontaktu który chcesz usunąć: ")
        if imie in ksiazka:
            del ksiazka[imie]
            print("Kontakt został usunięty.")
        else:
            print("Nie ma takiego kontaktu.")

W tej opcji użytkownik może usunąć kontakt. Najprostszą metodą jest użycie instrukcji del która usuwa w tym wypadku jeden element wskazany przez użytkownika.

4 - wydruk nazw

    elif wybor == "4": # wydruk nazw
        if ksiazka:
            print("Oto Twoje nazwy kontaktów:")
            for imie in sorted(ksiazka):
                print(imie)
        else:
            print("Brak kontaktów.")

Tutaj użyłam funkcji sorted aby nazwy kontaktów zostały wypisane alfabetycznie. Nazwy kontaktów zostają wyświetlone w formie listy. A jeżeli nic nie ma to użytkownik się o tym dowie 😄

5 - wydruk całej książki

    elif wybor == '5': # wydruk całej książki z kontaktami
        if ksiazka:
            print("Oto wszystkie Twoje kontakty:")
            for imie, numer in sorted(ksiazka.items()):
                print(f"Nazwa: {imie} - kontakt: {numer}")
        else:
            print("Brak kontaktów.")

W tej opcji dzieje się dokładnie to samo co wyżej - kontakty zostają wypisane w kolejności alfabetycznej jako Nazwa: {jakaś nazwa} - kontakt: {jakiś kontakt} a jak kontaktów brak, to Brak kontaktów 😄

6 - wyszukaj kontakt

    elif wybor == '6': # szukanie po części nazwy
        wyszukaj = input("Podaj część nazwy kontaktu którego szukasz: ").strip().lower()
        znalezione = {imie: numer for imie, numer in ksiazka.items() if imie.lower().startswith(wyszukaj)}
        if znalezione:
            for imie, numer in znalezione.items():
                print(f"Nazwa: {imie} kontakt: {numer}")
        else:
            print("Nie znaleziono pasujących wyników.")

Ta część kodu to moja inwencja twórcza z próbowaniem różnych funkcji i możliwości. Zdecydowałam się dodać tę opcję ponieważ zdarza się, że ktoś nie do końca pamięta jak zapisał kontakt i tutaj ma szanse sobie przypomnieć (pod warunkiem, że wie chociaż od jakiej litery się zaczyna).

Najpierw zadeklarowałam zmienną wyszukaj do której użytkownik wpisuje czego szuka. Zastosowałam strip().lower() żeby program zignorował nadprogramowe puste znaki i wyrzucił kontakty zapisane zarówno z dużych jak i małych liter. Następna zmienna znalezione dopasowuje to co zostało przypisane do wyszukaj i porównuje z tym co znajduje się w słowniku (książce kontaktowej). Użyłam startswith(wyszukaj) ponieważ chciałam uniknąć sytuacji gdzie użytkownik wpisał np. samą literę A i wyrzuciło mu wszystkie kontakty w których występuje ta litera. To by było trochę niewygodne i mało czytelne bo takich kontaktów mogłoby być dużo a nam zależy tylko na wyszukaniu tego konkretnego. Jeżeli program znajdzie pasujące kontakty wyrzuci listę wszystkich pasujących zapisów a jeżeli nie to poinformuje, że takich nie ma.

0 - zapisz i zamknij

    elif wybor == '0': #zamknij
        zapis = input("Czy chcesz zapisać zmiany? [t/n]: ").lower()
        if zapis == "t":
            with open(plik, "w", encoding="utf=8") as f:
                json.dump(ksiazka, f, ensure_ascii=False, indent=4, sort_keys=True)
            print("Zmiany zostały zapisane.")
        else:
            print("Zmiany nie zostały zapisane.")
        print("Koniec programu. Do zobaczenia.")
        break

I tak oto prezentuje się ostatnia opcja która kończy pętlę while a tym samym "zamyka" program. Na początku opcję zapisu do pliku miałam bezpośrednio w wybranych opcjach, ale jest to niewygodne i przy większych projektach może powodować problemy w funkcjonowaniu (np. błędy) i dodatkowo spowalniać cały program. Dlatego opcję zapisu pliku dodałam na samym końcu dając też możliwość użytkownikowi wyboru czy w ogóle chce zmiany zapisać.

Jeżeli użytkownik chce zapisać zmiany wtedy program otwiera plik i zapisuje w nim wszystko co do tej pory zostało utworzone w "tymczasowym" słowniku. Jak widać dopiero tutaj pojawia się sort_keys=True dlatego funkcja sorted była potrzebna w opcji 4 i 5. Gdy użytkownik kliknie dowolny inny przycisk który nie jest "t" wtedy program nie zapisze zmian. Następnie następuje break który kończy pętlę while i kończy program.

Kombinowałam również z zapisywaniem, żeby nazwy kontaktów, niezależnie od wyboru użytkownika, zawsze były zapisywane z dużej litery (niestety sortowanie alfabetyczne odbywa się od A-Z a potem od a-z, więc może to sprawiać problemy) dla przejrzystości zapisu i uniknięcia duplikatów. Próbowałam też stwarzać inne opcje jak np. tworzenie tabelki itp.

Jeżeli chodzi o "tabelkę" to wiem, że można skorzystać z formatowania, np. f"Nazwa: {imie: <5} - Kontakt: {numer}", ale w przypadku wpisania długich nazw to się i tak wszystko rozchodzi, jak tutaj:

Więc albo trzeba byłoby wprowadzić ograniczenia, że np. nazwa kontaktu nie może być dłuższa niż ileś znaków albo pokombinować w inny sposób, ale to już jest czysta kosmetyka i nie wiem czemu tak bardzo się na to uwzięłam 😁