admin 0Comment

Dziś zajmujemy się kolejnym zadaniem z internetowego kursu Automate the Boring Stuff with Python. Jest to drugie zadanie z rozdziału siódmego. Oto jego tekst:

Regex Version of strip()

Write a function that takes a string and does the same thing as the strip() string method. If no other arguments are passed other than the string to strip, then whitespace characters will be removed from the beginning and end of the string. Otherwise, the characters specified in the second argument to the function will be removed from the string.

Musimy więc we własnej funkcji odwzorować metodę strip(). Warto zacząć od zorientowania się jak działa ta metoda w Pythonie. Jeżeli zastosujemy ją bez dodatkowego parametru, metoda ta usunie wszelkie whitespace characters (takie jak spacja, tab czy newline) na początku i na końcu naszego stringa:

>>> spam = '   Bacon   '

>>> spam.strip()

'Bacon'

Z dodatkowym parametrem, to on zostanie usunięty z początku i końca podanego stringa:

>>> spam = 'SpamBaconSpam'

>>> spam.strip('Spam')

'Bacon'

To nie wszystko. niezależnie od kolejności znaków w łańcuchu podanym jako parametr, także zostaną one usunięte:

>>> spam = 'SpamBaconSpam'

>>> spam.strip('ampS')

'Bacon'

Widzimy więc, że nie jest to proste usunięcie danego stringa (lub whitespace) z początku i końca innego łańcucha znaków, co raczej usunięcie grupy łańcuchów znaków, w tym umieszczone wielokrotnie:

>>> spam = 'SpamSpamBaconSpamSpam'

>>> spam.strip('ampS')

'Bacon'

Bez zagłębienia się w te szczegóły moglibyśmy zaimplementować rozwiązanie, które sprawdza się tylko w najprostszych przypadkach i nie jest odpowiedzią na postawione przed nami zadanie.

Zacznijmy implementację rozwiązania. Ma być stworzone w formie funkcji, musimy więc zastanowić się, jakie powinna ona przyjmować argumenty. Na pewno musimy dostarczyć tekst, na którym chcemy ją zastosować. Drugi argument ma natomiast być opcjonalny, ponieważ funkcja powinna zadziałać zarówno z nim, jak i bez podania go (wtedy usunie wszystkie whitespace characters z początku i końca dostarczonego tekstu). Mamy zamiar wykorzystać tutaj także tzw. regular expressions (to ich dotyczy rozdział we wspomnianym kursie, z którego pochodzi zadanie – jeśli nie wiesz czym są regular expressions, bardzo polecam Ci sięgnąć do tego rozdziału, jest dostępny online), musimy więc zaimportować odpowiedni moduł:

import re

def customStrip(string, toRemove=None):

To dobry początek dla naszego rozwiązania. Domyślną wartością (czyli taką, która będzie obowiązywała, jeśli użytkownik nie poda innej podczas wywoływania funkcji) dla naszego argumentu opcjonalnego jest None.

Z tekstu zadania wiemy, że musimy poradzić sobie z dwiema opcjami. Pierwsza to ta, w której użytkownik nie podaje parametru opcjonalnego (i argument toRemove przyjmuje wartość domyślną None), w drugiej opcji – podaje taki parametr i funkcja usuwa go (a raczej – jego elementy) z początku i końca stringa, tak jak „oryginalna” funkcja strip(). Zacznijmy więc od pierwszej opcji.

Jako, że jest to ćwiczenie dla osób początkujących, rozłożymy wykorzystywanie wyrażeń regularnych na etapy, zajmując się po kolei początkiem i końcem podanego do funkcji łańcucha znaków. Chcemy więc najpierw usunąć wszystkie whitespace characters (symbolizowane w kodzie przez ‚\s’) z początku łańcucha znaków, a następnie te z jego końca. Oto jak mogłoby to wyglądać:

import re

def customStrip(string, toRemove=None):
    if toRemove== None:
        beginRegex = re.compile(r'^\s+')
        string = beginRegex.sub('', string)
        endRegex = re.compile(r'\s+$')
        string = endRegex.sub('', string)
    else:

Używamy tutaj metody sub(), która podmienia w podanym stringu elementy odpowiadające podanemu wyrażeniu regularnemu.

Oczywiście po ‚else’ zaimplementujemy drugą opcję, czyli sytuację gdy użytkownik podaje dodatkowy parametr. Pamiętajmy jednak, że ma ona usuwać grupy łańcuchów znaków, w tym umieszczone wielokrotnie (zajrzyj na początek tego testu jak działa „oryginalna” funkcja strip(), jeśli nie pamiętasz dlaczego):

else:
        beginRegex = re.compile(r'^['+ toRemove+']+')
        string = beginRegex.sub('', string)
        endRegex = re.compile(r'['+ toRemove+']+$')
        string = endRegex.sub('', string)
    return string

Podany przez użytkownika argument toRemove (a dokładniej – jego zawartość) umieściliśmy w naszym wyrażeniu regularnym w nawiasach [], co oznacza, że zostanie potraktowany właśnie, jako grupa znaków, a nie konkretny łańcuch do usunięcia. W praktyce oznacza to np., że, podając argument ‚ampS’, z początku i końca stringa zostaną usunięte m.in. fragmenty takie jak ‚Spam’ czy ‚mapS’. Gdybyśmy umieścili go bez nawiasów, usuwane byłyby tylko fragmenty odpowiadające dokładnie podanemu argumentowi ‚ampS’. Jak wygląda więc całość naszej implementacji w tym momencie?:

import re

def customStrip(string, toRemove=None):
    if toRemove== None:
        beginRegex = re.compile(r'^\s+')
        string = beginRegex.sub('', string)
        endRegex = re.compile(r'\s+$')
        string = endRegex.sub('', string)
    else:
        beginRegex = re.compile(r'^['+ toRemove+']+')
        string = beginRegex.sub('', string)
        endRegex = re.compile(r'['+ toRemove+']+$')
        string = endRegex.sub('', string)
    return string

Na tym etapie nauki jest to jak najbardziej satysfakcjonujące nas rozwiązanie, jednak możemy pokusić się o próbę jego skrócenia.  Widzimy między innymi, że dwie części naszej funkcji wyglądają podobnie,  możemy więc spróbować czy taka wersja toRemove będzie działać:

import re

def customStrip(string, toRemove='\s'):
    beginRegex = re.compile(r'^['+ toRemove+']+')
    string = beginRegex.sub('', string)
    endRegex = re.compile(r'['+ toRemove+']+$')
    string = endRegex.sub('', string)
    return string

Jak widzicie, pozbyliśmy się też niepotrzebnego if-a i żonglowania zawartością argumentu opcjonalnego, ustawiając jego wartość domyślną od razu na istniejącą nas zawartość w przypadku nie podania go przez użytkownika. Przetestujcie sami, czy we wszystkich zakładanych przez nas na początku tego tekstu sytuacjach funkcja zachowa się tak, jak jej oryginalny odpowiednik.

Dodaj komentarz