Indeksowanie w Pythonie

2011-05-15 16:34

Przeglądając jakiś rzeczywisty kod w języku Python można często natknąć się na nietypowe wykorzystanie operatora nawiasów kwadratowych. Tradycyjnie znaki te służą do indeksowania tablic, co w językach kompilowanych bezpośrednio do kodu maszynowego równa się prostej operacji na wskaźnikach:

  1. int tab[N];
  2. // ...
  3. assert( tab[i] == *(tab + i) );

Ponieważ jednak Python nie jest takim językiem, jego twórcy pozwolili sobie na to, by zawarte w nim kilogramy warstw abstrakcji oferowały dodatkową funkcjonalność również przy tak trywialnym zagadnieniu. W rezultacie indeksowanie tablic (a właściwie list, w tym i łańcuchów znaków) jest tu operacją, która często ukrywa w sobie znacznie bardziej skomplikowaną logikę niż to widać na pierwszy rzut oka.

Zacznijmy od tego, że w dopuszczalnymi indeksami są nie tylko dodatnie, ale i ujemne liczby całkowite. Oznaczają one dostęp do końcowych elementów tablicy: -1 do pierwszego od końca, -2 do drugiego, i tak dalej. Być może nie wydaje się logiczne to, że elementy tab[0] i tab[-1] są na przeciwnych krańcach listy podczas gdy ich indeksy różnią zaledwie o jeden. Uzasadnieniem jest tu odniesienie do indeksowania od końca w innych językach, czyli tab[tab.length() - i]. W Pythonie po prostu pomija się jawne zapisanie odwołania do długości tablicy.

Znacznie bardziej interesującym aspektem indeksowania jest użycie dwukropka (:). W zasadzie to zamienia on wówczas całą operację na “krojenie” (slice) tablicy, bo pozwala on na na wybór nie jednego elementu, a całego przedziału. Dokładniej mówiąc tab[i:j] oznacza fragment tablicy wyznaczony półotwartym zakresem indeksów [i; j). Kawałek ten zawiera więc tab[i], ale pomija tab[j]; jest to analogiczne chociażby do iteratorów begin() i end() w kontenerach STL.
To właśnie slicing jest tą nietypową operacją, która dla niewprawnego oka wygląda cokolwiek zagadkowo. Jest tak zwłaszcza wtedy, gdy wykorzystuje ona możliwość pominięcia jednego z krańców przedziału, który to jest wówczas “dociągany” do odpowiedniego krańca całej listy.

Łącząc wszystkie te zawiłości możemy już rozszyfrować większość często występujących przypadków użycia indeksowania w Pythonie:

  1. tab[1:] # tablica bez pierwszego elementu
  2. tab[:-1] # tablica bez ostatniego elementu
  3. tab[:n] # co najwyżej n początkowych elementów tablicy
  4. tab[-n:] # n > 0 końcowych elementów tablicy
  5.  
  6. # początkowy ciąg aż do wystąpienia znaku @
  7. username = email[:email.index('@')]
  8.  
  9. # końcowy ciąg począwszy od ostatniej kropki
  10. extension = filename[filename.rindex('.'):]

Dwa ostatnie przykłady pokazują też, że tego rodzaju operacje są bardzo przydatne podczas przetwarzania łańcuchów znaków, które to “przypadkiem” są również swego rodzaju tablicami.

Tags: , , ,
Author: Xion, posted under Programming »


6 comments for post “Indeksowanie w Pythonie”.
  1. vashpan:
    May 15th, 2011 o 17:26

    Takie myki nie tylko w wysokopoziomowych jezykach skryptowych – jezyk D posiada podobne “featury” jezeli chodzi o indeksowanie tablic i stringow. Implementacja czegos takiego jest przeciez w miare prosta takze na najnizszym poziomie. To tylko tablice ;)

  2. MSM:
    May 15th, 2011 o 18:56

    Dodałbym jeszcze jeden fajny feature – możliwość dodania drugiego dwukropka i trzeciego parametru po nim – pozwala to brać co n-ty element. Przykłady:


    >>> tab = [1, 2, 3, 4, 5, 6]
    >>> tab[::2]
    [1, 3, 5]
    >>> tab[2:-2:2]
    [3]
    >>> tab[1::2]
    [2, 4, 6]

    Jak widać jest to jeszcze bardziej niejasne dla kogoś kto nie wie o co chodzi ;)

  3. Sebas86:
    May 15th, 2011 o 21:37

    MSM, w przypadku trójargumentowym, nie trzeci a drugi parametr wskazuje skok – skrajne argumenty wskazują początek i koniec.

    >>> a = [1,2,3,4,5,6,7]
    >>> a[0:-1:5]
    [1, 6]

    Zapis jest niejasny tylko dla ludzi, którzy nigdy wcześniej nic nie mieli z nim wspólnego. ;)

    Ogólnie przydatna rzecz, podobna konstrukcja w Matlabie jest bardzo użyteczna, chociaż o nieco innym skutku ubocznym – nie jest związana bezpośrednio z operatorem wyłuskania elment(u|ów) z tablicy, a generuje tablicę wartości, która może, ale nie musi, posłużyć do dalszego indeksowania.

  4. p:
    May 15th, 2011 o 21:49

    @Sebas86 Ale czy właśnie sam się nie negujesz? Wypisujesz tablicę od pierwszego do ostatniego elementu co 5 elementów, czyli właśnie [1,6] – trzeci parametr wskazuje na skok.

  5. Xion:
    May 16th, 2011 o 19:04

    W Pythonie trzeci parametr wskazuje na skok, w Matlabie drugi. Między innymi dlatego, że jest to tak mylące, to o tym nie wspominałem ;)

  6. gryf:
    May 17th, 2011 o 21:12

    Stringi (łańcuchy) można indeksować, jednak nie są one listami/tablicami – nie można ich modyfikować:

    1. >>> a = [1,2,3,4,5]
    2. >>> b = "razdwatrzy"
    3. >>> del(a[:-2])
    4. >>> a
    5. [4, 5]
    6. >>> del(b[:-2])
    7. Traceback (most recent call last):
    8.   File "", line 1, in
    9. TypeError: 'str' object does not support item deletion
    10. >>> b[3] = "f"
    11. Traceback (most recent call last):
    12.   File "", line 1, in
    13. TypeError: 'str' object does not support item assignment
Comments are disabled.
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.