Chcąc rozpocząć ścieżkę kariery jako programista Python, zastanawiałem się, jaki sens ma rozwiązywanie zadania „na kartce” podczas rekrutacji. W tym artykule podzielę się z Wami zadaniem, z którym miałem styczność. Przejdziemy krok po kroku proces jego wytwarzania oraz poznamy kilka właściwości języka.
Zadanie: zamiana ciągu
Naszym zadaniem jest zamiana ciągu `a4b3r6` na `aaaabbbrrrrrr`.
Pierwszą czynnością jest odnalezienie schematu. W tym przypadku podano w treści zadania, że cyfra jest większa od 0, a litera jest z alfabetu ASCII. Wskazano również, że takich par w ciągu wejściowym jest co najmniej 1. Wejście zawsze będzie tego formatu – nie jest wymagana obsługa błędów.
Do rozwiązania zadania użyłem Pythona w wersji 3.x. Wiedza, że Python jest językiem silnie dynamicznie typowanym jest niezbędna do rozpoczęcia pracy. Takie podejście ma swoje wady (wykonywanie operacji na typach, które wspierają tę operację) i zalety (brak potrzeby zarządzania pamięcią). Jest on również językiem interpretowanym – nie ma etapu kompilacji, jest niezależny od architektury, na której jest uruchamiany.
Podejście do rozwiązania zadania
Kiedy już znamy schemat, to wydaje się zasadnym, żeby użyć wyrażenia regularnego (RegEx). Cytując za klasykiem:
Jeżeli coś może zostać wykonane przez wyrażenie regularne, to na pewno robisz to źle.
Z tego powodu to rozwiązanie zostawimy sobie jako wisienkę na torcie, a zaczniemy od podejścia `brute force`.
`brute force`
Zauważmy, że cyfra jest na indeksie (tak, w Pythonie można traktować ciąg znaków jak tablicę) o 1 mniejszym niż litera. Iterując po całym ciągu wejściowym (w Pythonie iteruje się po elementach tablicy – funkcja `range` przekształca liczbę na odpowiednią tablicę; jeśli chcecie poznać cały wykaz możliwości, zachęcam do przeczytania dokumentacji), przy nieparzystej wartości indeksu (tutaj pojawi się cyfra), możemy pobrać literę i iterując ją tyle razy, na ile wskazuje cyfra, konkatenować (w Pythonie konkatenuje się znaki symbolem `+`, a także `+=`, gdzie znak po lewej stronie konkatenujemy ze znakiem po prawej stronie i przypisujemy do zmiennej po lewej stronie.
Wykorzystanie metod
Do łączenia ciągów znaków można również użyć tych metod) w pętli. Przykład, jak to zrobić, możecie sprawdzić w metodzie `stage_one`.
def stage_one(self, st: str) -> str: # brute force
return_string = ''
for i in range(len(st)):
if i % 2 == 1:
for j in range(int(st[i])):
return_string += st[i - 1]
return return_string
Jeżeli lekko zoptymalizujemy poprzedni przykład i podzielimy ciąg wejściowy na paczki składające się z 2 elementów, to pętla główna metody wykona się tylko połowę razy. Użyłem również właściwości języka umożliwiającej „wycięcie” z tabeli zakresu indeksów, co możecie zobaczyć w metodzie `stage_two`.
def stage_two(self, st: str) -> str: # lets think about optimisation
return_string = ''
for i in range(int(len(st) / 2)):
st_temp = st[2 * i: 2 * (i + 1)]
for j in range(int(st_temp[1])):
return_string += st_temp[0]
return return_string
Kolejnym etapem usprawnienia będzie pozbycie się właśnie dodanego „wydzielania” przez zauważenie, że cyfra będzie zawsze w tym samym miejscu, dzięki czemu pozbywamy się jednej operacji: metoda `stage_three`.
def stage_three(self, st: str) -> str: # more optimisation
return_string = ''
for i in range(int(len(st) / 2)):
for j in range(int(st[2 * i + 1])):
return_string += st[2 * i]
return return_string
Metoda ‚stage_four’ wprowadza kolejną właściwość języka Python – mnożenie stringów. Jest właściwym mnożeniem stringów: `6*”alf” = “alfalfalfalfalfalf”`. Ta cecha pozwala nam całkowicie pozbyć się wewnętrznej pętli.
def stage_four(self, st: str) -> str: # let it lok nice too
return_string = ''
for i in range(len(st)):
if i % 2 == 0:
return_string += str(int(st[i + 1]) * st[i])
return return_string
Ta metoda byłaby wersją ostateczną – rozwiązaniem zadanego problemu. Po drobnych porządkach mamy metodę `stage_five`.
def stage_five(self, st: str) -> str: # final version
return_string = ''
for i in range(int(len(st) / 2)):
return_string += str(int(st[2 * i + 1]) * st[2 * i])
return return_string
Pojawia się możliwość przedstawienia wersji funkcyjnej tego rozwiązania – metoda `stage_functional`, w której zamiast pętli użyjemy metody `enumerate`, a żeby połączyć ciągi znaków, skorzystamy z metody `join` na pustym znaku.
def stage_functional(self, str:str) -> str:
return ''.join(map(lambda x: x[1] * int(str[x[0] + 1]) if x[0] % 2 == 0 else "", enumerate(list(str))))
Wyrażenie regularne
Istnieje drobna obawa o to, czy jesteśmy w stanie coś poradzić, jeżeli zamiast cyfry w ciągu znaków wejściowych pojawi się liczba – zmienią się warunki wejściowe. Tutaj najbardziej ogólnym, wymagającym najmniejszej ilości modyfikacji, rozwiązaniem będzie użycie wyrażeń regularnych – ich odmiana istnieje w języku Python w bibliotece ‚re’, która została zaimportowana i użyta w metodzie `stage_six`.
def stage_six(self, st: str) -> str: # regexp bonus :P
import re
return_string = ''
for st_temp in re.findall(r"\D+\d+", st):
return_string += str(int(st_temp[1]) * st_temp[0])
return return_string
O wyrażeniach regularnych są pisane książki mające kilkaset stron – każdy język ma swoje podejście do tematu. W dokumentacji biblioteki `re` można przeczytać, że `\d` jest znacznikiem cyfr, a `\D` jest znacznikiem liter.
Podsumujmy, co wiemy
Sprawdźmy, jakie informacje przekazaliśmy rekruterowi na temat naszej wiedzy o języku Python i podejściu do rozwiązania problemu:
- iteracja po tablicy elementów – funkcja `range`,
- ciąg znaków jest tablicą,
- konkatenacja z pomocą `+`, `+=`,
- wycinanie elementów tablicy,
- mnożenie stringów,
- wyrażenia regularne jako biblioteka,
- możliwe podejście funkcyjne,
- `join` jako metoda łączenia znaków, `enumerate` jako zamiennik pętli,
- pokazaliśmy, że potrafimy optymalizować swoje rozwiązanie,
- jesteśmy w stanie zastosować różne podejścia do problemu – wyrażenia regularne, programowanie funkcyjne.
Wnioski
Czy wykazanie się tą wiedzą i umiejętnościami będzie wystarczające do zapewnienia sobie stanowiska „Junior Python Developer”? Nie. Może być przydatne. Na pewno nie zaszkodzi 😊 Trenowanie na takich podstawowych przykładach, rozwijanie się oraz dokumentowanie tego będzie dodatkowym atutem.
***
Jeśli interesuje Cię obszar rekrutacji, zajrzyj koniecznie do innych artykułów naszych ekspertów oraz do ofert pracy 🙂
Zostaw komentarz