Zadanie

Napisz funkcję sprawdzającą, czy elementy krotki pierwszej znajdują się w krotce drugiej.

krotka1 = (3, 4, 5)
krotka2 = (1, 2, 3, 4, 5, 6, 7, 8, 9)

Wbrew pozorom zadanie to wcale nie jest tak banalne jak to wygląda. Dodatkowo z uwagi na cały wachlarz możliwości w jaki można podjeść do tego problemu przyniesie ono wartość zarówno początkującym jak i tym troszkę bardziej zaawansowanym. Koniec gadania czas przejść do działania. Jak zwykle zaczniemy od rozwiązań najprostszych (nie mylić z najgorszymi), tak aby potem przejść do bardziej złożonych pomysłów.

Rozwiązanie

Tak nie robić !!!

Wielu początkujących programistów języka Python na początku próbuje zrobić coś takiego:

krotka1 = (3, 4, 5)
krotka2 = (1, 2, 3, 4, 5, 6, 7, 8, 9)

def czy_krotka_w_krotce(krotka1, krotka2):
    if krotka1 in krotka2:
        return True
    else:
        return False

print(czy_krotka_w_krotce(krotka1, krotka2))

Dlaczego to nie zadziała ? Dodajmy, że poniższy kod działa i nawet nie zwraca błędu. Niestety nie robi on też tego czego od niego oczekiwaliśmy. Zamiast sprawdzić, czy elementy z krotka1 znajdują się w krotka2 sprawdziliśmy, czy cała krotka1 nie jest elementem krotka2. Aby powyższy warunek został spełniony krotka2 musiałaby wyglądać mniej więcej tak: krotka2 = (1, (3, 4, 5), 3, 4, 5, 6, 7, 8, 9)

Pomysł ze zliczaniem

krotka1 = (3, 4, 5)
krotka2 = (1, 2, 3, 4, 5, 6, 7, 8, 9)


def czy_krotka_w_krotce(krotka1, krotka2):
    licznik = 0

    for element in krotka1:
        if element in krotka2:
            licznik += 1

    if licznik == len(krotka1):
        return True
    else:
        return False

print(czy_krotka_w_krotce(krotka1, krotka2))

Od razu powiem, że nie jest to najładniejsze podejście, a już na pewno nie jest to podejście w duchu Pythona. Mimo to działa więc warto je omówić. Na początku tworzymy licznik i przypisujemy mu wartość 1. Następnie iterujemy się po wszystkich elementach krotka1. Jeśli dany element z krotka1 znajduje się również w krotka2 wtedy zwiększamy nasz licznik o jeden. Na koniec sprawdzamy, czy wartość naszego licznika jest równa ilości elementów w krotka1. Jeśli tak to znaczy, że wszystkie elementy z krotka1 odmeldowały się również w krotka2. Jeśli wartość naszego licznika byłaby o jeden krótsza niż ilość elementów w krotka1 to znaczy, że jednego z elementów krotka1 brakuje w krotka2.

Sprawdzanie leniwe

No w końcu jakiś sposób właściwy dla mnie :). No dobra bez żartów. O co chodzi z tym lenistwem ? Otóż w poprzednim przykładzie aby stwierdzić, czy krotka1 zawiera się w krotka2 musieliśmy sprawdzić wszystkie elementy krotka1. To dlatego, że dopiero na końcu był warunek sprawdzający. Zauważmy jednak, że jeżeli chociaż jeden (np. pierwszy) element z krotka1 nie odmelduje się w krotka2 to już wiemy, że wszystkie elementy krotka1 na 100% nie znajdą się w krotka2. W takim wypadku (jako, że jesteśmy leniwi) możemy przerwać dalsze sprawdzanie i ogłosić światu wynik. Im więcej mamy danych do sprawdzenia tym bardziej opłaca nam się takie podejście.

krotka1 = (3, 4, 5)
krotka2 = (1, 2, 3, 4, 5, 6, 7, 8, 9)


def czy_krotka_w_krotce(krotka1, krotka2):
    for element in krotka1:
        if element not in krotka2:
            return False

    return True

print(czy_krotka_w_krotce(krotka1, krotka2))

Dodatkowo nasz kod jest krótszy i łatwiejszy do zrozumienia.

Leniwy generator składany

Pewnie zastanawiałeś się czy da się nasz problem rozwiązać przy pomocy listy składanej. Oczywiście, że tak. My nawet pójdziemy o krok dalej i od razu posłużymy się generatorem. Oczywiście sam generator nie dałby pożądanego efektu. To co dzięki niemy osiągniemy to jedynie (albo aż) kolekcja składająca się z ”True” lub ”False” w zależności od tego czy udało się znaleźć konkretny element krotka1krotka2. Na szczęście z pomocą przychodzi funkcja all”. Zwraca ona ”True” jeśli wszystkie elementy kolekcji przekazanej jako parametr mają również wartość ”True”. Czyli w naszym przypadku zwróci ”True” jeśli wszystkie elementy z krotka1 były również w krotka2.

krotka1 = (3, 4, 5)
krotka2 = (1, 2, 3, 4, 5, 6, 7, 8, 9)


def czy_krotka_w_krotce(krotka1, krotka2):
    return all(
        True if element in krotka2 else False
        for element in krotka1
    )


print(czy_krotka_w_krotce(krotka1, krotka2))

Dodatkowo warto pamiętać, że użycie generatora wraz z funkcją all” również jest leniwe. Znaczy to, że w przypadku odnotowaniu pierwszego ”Falsefunkcjaall” przerywa dalsze sprawdzanie i natychmiastowo zwraca ”False” jako wynik całej operacji.

Set przychodzi z pomocą.

To rozwiązanie potraktujcie jako bonus i używajcie go z rozwagą. Tym razem najpierw kod, a potem omówienie.

krotka1 = (3, 4, 5)
krotka2 = (1, 2, 3, 4, 5, 6, 7, 8, 9)


def czy_krotka_w_krotce(krotka1, krotka2):
    return set(krotka1).issubset(krotka2)


print(czy_krotka_w_krotce(krotka1, krotka2))

Co my tu zrobiliśmy? Na początek zamieniliśmy krotka1 na kolekcję typuset”. Następnie użyliśmy metodyissubset” aby sprawdzić, czy nasz nowy ”set” jest podzbiorem krotka2. Wygląda prosto i schludnie. Jednak jest tu jeden niuans o którym musimy wspomnieć. Pamiętajmy, że w momencie tworzenia naszego nowego zbioru (set) niejako pozbywamy się z krotka1 wszystkich duplikatów. Zatem z krotki (1, 1, 1) po zmianie na ”set” zostałoby jedynie (1, ). Czyli jeśli interesowałoby nas czy w krotka2 znajdują się na przykład dwie wartości 2 to w ten sposób nie dało by się tego zrobić. Jeśli jednak taka informacja nas nie interesuje lub jesteśmy pewni, że nasza krotka1 nie ma powtórzeń to warto skorzystać z ”set” i jego metod.