5. Struktury danych

Ten rozdział opisuje bardziej szczegółowo niektóre rzeczy, które już poznaliście, oraz również wprowadza nowe elementy.

5.1. Więcej na temat list

Typ danych listy ma kilka więcej metod. To wszystkie metody obiektów list:

list.append(x)

Dodaje element na końcu listy. Ekwiwalent a[len(a):] = [x].

list.extend(iterable)

Rozszerza listę przez dodanie wszystkich elementów iterable’a. Ekwiwalent a[len(a):] = iterable.

list.insert(i, x)

Wstawia element na podaną pozycję. Pierwszy argument jest indeksem elementu, przed którym wstawiamy, więc a.insert(0, x) wstawia na początek listy a a.insert(len(a), x) odpowiada a.append(x).

list.remove(x)

Remove the first item from the list whose value is x. It is an error if there is no such item.

list.pop([i])

Usuwa element z podanej pozycji na liście i zwraca go. Jeśli indeks nie jest podany, a.pop() usuwa i zwraca ostatni element listy. (Nawiasy kwadratowe dookoła i w sygnaturze metody oznaczają, że ten parametr jest opcjonalny, nie że powinieneś wpisać nawiasy kwadratowe w tym miejscu. Taką notację będziesz często widzieć w dokumentacji biblioteki Pythona.)

list.clear()

Usuwa wszystkie elementy z listy. Ekwiwalent del a[:].

list.index(x[, start[, end]])

Return zero-based index in the list of the first item whose value is x. Raises a ValueError if there is no such item.

Opcjonalne argumenty start i end są interpretowane jak w notacji slice i służą do ograniczenia wyszukiwania do szczególnej podsekwencji listy. Zwracany indeks jest wyliczany względem początku pełnej sekwencji, nie względem argumentu start.

list.count(x)

Zwraca liczbę razy, jaką x występuje liście.

list.sort(key=None, reverse=False)

Sortuje elementy listy w miejscu (argumenty mogą służyć do dostosowania sortowania, patrz sorted() po ich wyjaśnienie).

list.reverse()

Odwraca elementy listy w miejscu.

list.copy()

Zwraca płytką kopię listy. Ekwiwalent a[:].

Przykład, który używa większość metod listy:

>>> fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
>>> fruits.count('apple')
2
>>> fruits.count('tangerine')
0
>>> fruits.index('banana')
3
>>> fruits.index('banana', 4)  # Find next banana starting a position 4
6
>>> fruits.reverse()
>>> fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']
>>> fruits.append('grape')
>>> fruits
['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']
>>> fruits.sort()
>>> fruits
['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']
>>> fruits.pop()
'pear'

Mogłeś(-aś) zauważyć, że metod takie jak insert, remove lub sort, które tylko modyfikują listę, nie mają wyświetlonej zwracanej wartości – zwracają one domyślne None. 1 To zasada projektowa dla wszystkich mutowalnych typów danych w Pythonie.

5.1.1. Używanie list jako stosów

Metody listy ułatwiają używanie listy jako stosu, gdzie ostatni element dodany jest pierwszym elementem pobieranym („last-in, first-out”). Aby dodać element na wierzch stosu, użyj append(). Aby pobrać element z wierzchu stosu, użyj pop() bez podanego indeksu. Na przykład:

>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack.append(7)
>>> stack
[3, 4, 5, 6, 7]
>>> stack.pop()
7
>>> stack
[3, 4, 5, 6]
>>> stack.pop()
6
>>> stack.pop()
5
>>> stack
[3, 4]

5.1.2. Używanie list jako kolejek

Można też używać list jako kolejek, gdzie pierwszy element dodany jest pierwszym elementem pobieranym („first-in, first-out”); jednakże listy nie są wydajne do tego celu. Appendy i popy na końcu listy są szybkie, lecz inserty i popy na początku listy są wolne (ponieważ wszystkie inne elementy muszą zostać przesunięte o jeden).

Aby zaimplementować kolejkę, użyj collections.deque, która została zaprojektowana, by mieć szybkie appendy i popy na obu końcach. Na przykład:

>>> from collections import deque
>>> queue = deque(["Eric", "John", "Michael"])
>>> queue.append("Terry")           # Terry arrives
>>> queue.append("Graham")          # Graham arrives
>>> queue.popleft()                 # The first to arrive now leaves
'Eric'
>>> queue.popleft()                 # The second to arrive now leaves
'John'
>>> queue                           # Remaining queue in order of arrival
deque(['Michael', 'Terry', 'Graham'])

5.1.3. List comprehensions

List comprehensions są zwięzłym sposobem na tworzenie list. Powszechne zastosowania to tworzenie nowych list, gdzie każdy element jest wynikiem jakichś operacji zastosowanych do każdego elementu innej sekwencji lub iterable’a lub do tworzenia podsekwencji tych elementów, które spełniają określony warunek.

Na przykład załóżmy, że chcemy stworzyć listę kwadratów, jak tu:

>>> squares = []
>>> for x in range(10):
...     squares.append(x**2)
...
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Zwróć uwagę, że ten kod tworzy (lub nadpisuje) zmienną o nazwie x, która wciąż istnieje po wykonaniu pętli. Możemy obliczyć listę kwadratów bez żadnych efektów ubocznych w następujący sposób:

squares = list(map(lambda x: x**2, range(10)))

lub równoważnie:

squares = [x**2 for x in range(10)]

co jest bardziej zwięzłe i czytelne.

A list comprehension consists of brackets containing an expression followed by a for clause, then zero or more for or if clauses. The result will be a new list resulting from evaluating the expression in the context of the for and if clauses which follow it. For example, this listcomp combines the elements of two lists if they are not equal:

>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

i jest odpowiednikiem:

>>> combs = []
>>> for x in [1,2,3]:
...     for y in [3,1,4]:
...         if x != y:
...             combs.append((x, y))
...
>>> combs
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

Zwróć uwagę, że kolejność instrukcji for i if jest taka sama w obu fragmentach kodu.

Jeśli wyrażenie jest krotką (tak jak (x, y) w poprzednim przykładzie), musi być wzięte w nawias.

>>> vec = [-4, -2, 0, 2, 4]
>>> # create a new list with the values doubled
>>> [x*2 for x in vec]
[-8, -4, 0, 4, 8]
>>> # filter the list to exclude negative numbers
>>> [x for x in vec if x >= 0]
[0, 2, 4]
>>> # apply a function to all the elements
>>> [abs(x) for x in vec]
[4, 2, 0, 2, 4]
>>> # call a method on each element
>>> freshfruit = ['  banana', '  loganberry ', 'passion fruit  ']
>>> [weapon.strip() for weapon in freshfruit]
['banana', 'loganberry', 'passion fruit']
>>> # create a list of 2-tuples like (number, square)
>>> [(x, x**2) for x in range(6)]
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
>>> # the tuple must be parenthesized, otherwise an error is raised
>>> [x, x**2 for x in range(6)]
  File "<stdin>", line 1, in <module>
    [x, x**2 for x in range(6)]
               ^
SyntaxError: invalid syntax
>>> # flatten a list using a listcomp with two 'for'
>>> vec = [[1,2,3], [4,5,6], [7,8,9]]
>>> [num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Wyrażenia listowe mogą zawierać złożone wyrażenia i zagnieżdżone funkcje:

>>> from math import pi
>>> [str(round(pi, i)) for i in range(1, 6)]
['3.1', '3.14', '3.142', '3.1416', '3.14159']

5.1.4. Zagnieżdżone wyrażenia listowe

Wyrażeniem wyjściowym w wyrażeniu listowym może być każde arbitralne wyrażenie, włączając inne wyrażenie listowe.

Rozważmy następujący przykład macierzy 3-na-4 zaimplementowanej jako lista trzech list o długości 4:

>>> matrix = [
...     [1, 2, 3, 4],
...     [5, 6, 7, 8],
...     [9, 10, 11, 12],
... ]

Następujące wyrażenie listowe przetransponuje wiersze i kolumny:

>>> [[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

Jak widzieliśmy w poprzedniej sekcji, zagnieżdżone wyrażenie listowe jest ewaluowane w kontekście for, które po nim następuje, więc ten przykład jest równoważny temu:

>>> transposed = []
>>> for i in range(4):
...     transposed.append([row[i] for row in matrix])
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

który z kolei jest taki sam jak:

>>> transposed = []
>>> for i in range(4):
...     # the following 3 lines implement the nested listcomp
...     transposed_row = []
...     for row in matrix:
...         transposed_row.append(row[i])
...     transposed.append(transposed_row)
...
>>> transposed
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

W prawdziwym świecie powinieneś(-nnaś) preferować wbudowane funkcje w złożonych instrukcjach przepływu. Funkcja zip() bardzo się przyda w tym przypadku:

>>> list(zip(*matrix))
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

W Rozpakowywanie listy argumentów znajdziesz wyjaśnienie znaku gwiazdki w tej linii.

5.2. The del statement

There is a way to remove an item from a list given its index instead of its value: the del statement. This differs from the pop() method which returns a value. The del statement can also be used to remove slices from a list or clear the entire list (which we did earlier by assignment of an empty list to the slice). For example:

>>> a = [-1, 1, 66.25, 333, 333, 1234.5]
>>> del a[0]
>>> a
[1, 66.25, 333, 333, 1234.5]
>>> del a[2:4]
>>> a
[1, 66.25, 1234.5]
>>> del a[:]
>>> a
[]

del można również użyć do usuwania całych zmiennych:

>>> del a

Odniesienie się do nazwy a odtąd jest błędem (przynajmniej dopóki nie przypisana jest do niej inna wartość). Później odnajdziemy więcej zastosowań dla del.

5.3. Krotki i sekwencje

Widzieliśmy, że listy i ciągi znaków mają wiele wspólnych własności, takich jak indeksowanie i operacje slice. Są one dwoma przykładami sekwencyjnych typów danych (patrz Sequence Types — list, tuple, range). Jako że Python jest ewoluującym językiem, mogą zostać dodane inne sekwencyjne typy danych. Jest też inny standardowy sekwencyjny typ danych: krotka.

Krotka składa się z kilku wartości rozdzielonych przecinkami, na przykład:

>>> t = 12345, 54321, 'hello!'
>>> t[0]
12345
>>> t
(12345, 54321, 'hello!')
>>> # Tuples may be nested:
... u = t, (1, 2, 3, 4, 5)
>>> u
((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
>>> # Tuples are immutable:
... t[0] = 88888
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> # but they can contain mutable objects:
... v = ([1, 2, 3], [3, 2, 1])
>>> v
([1, 2, 3], [3, 2, 1])

Jak widzisz na wyjściu krotki zawsze są otoczone nawiasami, tak aby zagnieżdżone krotki były poprawnie interpretowane; wpisać je można z lub bez otaczających nawiasów, chociaż często nawiasy są i tak potrzebne (jeśli krotka jest częścią większego wyrażenia). Nie da się przypisać wartości do pojedynczych elementów krotki, ale da się stworzyć krotki, które zawierają mutowalne obiekty, takie jak listy.

Mimo że krotki mogą wydawać się podobne do list, często są używane w innych sytuacjach i do innych celów. Krotki są niemutowalne i zazwyczaj zawierają heterogeniczne sekwencje elementów, do których dostęp uzyskuje się przez rozpakowywanie (patrz później w tej sekcji) lub indeksowanie (lub nawet przez atrybut w przypadku namedtuples). Listy są mutowalne i ich elementy są zazwyczaj homogeniczne i dostęp do nich uzyskuje się przez iterowanie po liście.

Specjalnym problemem jest konstrukcja krotek zawierających 0 lub 1 element: składnia przewiduje na to kilka sposobów. Puste krotki można konstruować pustą parą nawiasów; krotkę z jednym elementem można skonstruować umieszczając przecinek za wartością (nie wystarczy otoczyć pojedynczej wartości nawiasami). Brzydkie, ale działa. Na przykład:

>>> empty = ()
>>> singleton = 'hello',    # <-- note trailing comma
>>> len(empty)
0
>>> len(singleton)
1
>>> singleton
('hello',)

Instrukcja t = 12345, 54321, 'hello!' jest przykładem pakowania krotki: wartości 12345, 54321 i 'hello!' są razem zapakowane w krotkę. Możliwa jest również odwrotna operacja:

>>> x, y, z = t

Takie coś nazywane jest, odpowiednio, rozpakowywaniem sekwencji. Takie rozpakowywanie wymaga, aby po lewej stronie znaku równości było tyle samo zmiennych, ile jest elementów w sekwencji. Zauważcie, że wielokrotne przypisanie jest kombinacją pakowania i rozpakowywania sekwencji.

5.4. Zbiory

Python ma również typ danych dla zbiorów. Zbiór jest nieuporządkowaną kolekcją bez zduplikowanych elementów. Podstawowe użycia to sprawdzenie zawierania i eliminacja duplikatów. Obiekty zbiorów wspierają też operacje matematyczne jak suma, iloczyn, różnica i różnica symetryczna zbiorów.

Zbiory można stworzyć używając nawiasów klamrowych lub funkcji set(). Uwaga: aby stworzyć pusty zbiór, musisz użyć set(), nie {}; to drugie tworzy pusty słownik, strukturę danych, którą omówimy w następnej sekcji.

Poniżej krótka demonstracja:

>>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
>>> print(basket)                      # show that duplicates have been removed
{'orange', 'banana', 'pear', 'apple'}
>>> 'orange' in basket                 # fast membership testing
True
>>> 'crabgrass' in basket
False

>>> # Demonstrate set operations on unique letters from two words
...
>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a                                  # unique letters in a
{'a', 'r', 'b', 'c', 'd'}
>>> a - b                              # letters in a but not in b
{'r', 'd', 'b'}
>>> a | b                              # letters in a or b or both
{'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
>>> a & b                              # letters in both a and b
{'a', 'c'}
>>> a ^ b                              # letters in a or b but not both
{'r', 'd', 'b', 'm', 'z', 'l'}

Podobnie do wyrażeń listowych, są wspierane również wyrażenia zbiorów:

>>> a = {x for x in 'abracadabra' if x not in 'abc'}
>>> a
{'r', 'd'}

5.5. Słowniki

Innym przydatnym typem danych wbudowanym w Pythona jest słownik (patrz Mapping Types — dict). Słowniki w innych językach czasem występują jako „pamięci asocjacyjne” albo „tablice asocjacyjne”. W przeciwieństwie do sekwencji, które są indeksowane zakresem liczb, słowniki są indeksowane przez klucze, które mogą być dowolnym niemutowalnym typem; ciągi znaków i liczby zawsze mogą być kluczami. Można użyć krotek, jeśli zawierają tylko ciągi znaków, liczby lub krotki; jeśli krotka zawiera choć jeden mutowalny obiekt, bezpośrednio lub pośrednio, nie można jej użyć jako klucza. Nie możesz używać list jako kluczy, jako że listy mogą być modyfikowane „w miejscu” przy użyciu przypisań do indeksu, przypisań do slice’ów lub metod jak append() i extend().

It is best to think of a dictionary as an unordered set of key: value pairs, with the requirement that the keys are unique (within one dictionary). A pair of braces creates an empty dictionary: {}. Placing a comma-separated list of key:value pairs within the braces adds initial key:value pairs to the dictionary; this is also the way dictionaries are written on output.

Głównymi operacjami na słowniku są umieszczanie wartości pod jakimś kluczem oraz wyciąganie wartości dla podanego klucza. Możliwe jest również usunięcie pary klucz:wartość przy użyciu del. Jeśli umieścisz wartość używając klucza, który już jest w użyciu, stara wartość powiązana z tym kluczem zostanie zapomniana. Próba wyciągnięcia wartości przy użyciu nieistniejącego klucza zakończy się błędem.

Performing list(d.keys()) on a dictionary returns a list of all the keys used in the dictionary, in arbitrary order (if you want it sorted, just use sorted(d.keys()) instead). 2 To check whether a single key is in the dictionary, use the in keyword.

Mały przykład z użyciem słownika:

>>> tel = {'jack': 4098, 'sape': 4139}
>>> tel['guido'] = 4127
>>> tel
{'sape': 4139, 'guido': 4127, 'jack': 4098}
>>> tel['jack']
4098
>>> del tel['sape']
>>> tel['irv'] = 4127
>>> tel
{'guido': 4127, 'irv': 4127, 'jack': 4098}
>>> list(tel.keys())
['irv', 'guido', 'jack']
>>> sorted(tel.keys())
['guido', 'irv', 'jack']
>>> 'guido' in tel
True
>>> 'jack' not in tel
False

Konstruktor dict() buduje słowniki bezpośrednio z sekwencji par klucz-wartość:

>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'jack': 4098, 'guido': 4127}

Dodatkowo można użyć wyrażeń słownikowych to tworzenia słowników z podanych wyrażeń klucza i wartości:

>>> {x: x**2 for x in (2, 4, 6)}
{2: 4, 4: 16, 6: 36}

Kiedy klucze są prostymi ciągami znaków, czasem łatwiej jest podać pary używając argumentów nazwanych:

>>> dict(sape=4139, guido=4127, jack=4098)
{'sape': 4139, 'jack': 4098, 'guido': 4127}

5.6. Techniki pętli

Podczas iterowania po słownikach, klucz i odpowiadającą mu wartość można pobrać w tym samym czasie używając metody items().

>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
>>> for k, v in knights.items():
...     print(k, v)
...
gallahad the pure
robin the brave

Przy iterowaniu po sekwencji, indeks pozycyjny i odpowiadającą mu wartość można pobrać w tym samym czasie używając funkcji enumerate().

>>> for i, v in enumerate(['tic', 'tac', 'toe']):
...     print(i, v)
...
0 tic
1 tac
2 toe

Aby przeiterować po dwóch lub więcej sekwencjach w tym samym czasie, elementy mogą zostać zgrupowane funkcją zip().

>>> questions = ['name', 'quest', 'favorite color']
>>> answers = ['lancelot', 'the holy grail', 'blue']
>>> for q, a in zip(questions, answers):
...     print('What is your {0}?  It is {1}.'.format(q, a))
...
What is your name?  It is lancelot.
What is your quest?  It is the holy grail.
What is your favorite color?  It is blue.

Aby przeiterować po sekwencji od końca, najpierw określ sekwencję w kierunku „do przodu” a następnie wywołaj funkcję reversed().

>>> for i in reversed(range(1, 10, 2)):
...     print(i)
...
9
7
5
3
1

Aby przeiterować po sekwencji w posortowanej kolejności, użyj funkcji sorted(), która zwraca nową posortowaną listę pozostawiając listę źródłową niezmienioną.

>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
>>> for f in sorted(set(basket)):
...     print(f)
...
apple
banana
orange
pear

Czasem kusi, żeby zmienić listę podczas iterowania po niej; jednak często prościej i bezpieczniej jest stworzyć nową listę.

>>> import math
>>> raw_data = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]
>>> filtered_data = []
>>> for value in raw_data:
...     if not math.isnan(value):
...         filtered_data.append(value)
...
>>> filtered_data
[56.2, 51.7, 55.3, 52.5, 47.8]

5.7. Więcej na temat warunków

Warunki użyte w instrukcjach while i if mogą zawierać dowolne operatory, nie tylko porównania.

The comparison operators in and not in check whether a value occurs (does not occur) in a sequence. The operators is and is not compare whether two objects are really the same object; this only matters for mutable objects like lists. All comparison operators have the same priority, which is lower than that of all numerical operators.

Porównania mogą być układane w łańcuchy. Na przykład a < b == c sprawdza, czy a jest mniejsze od b i ponadto czy b równa się c.

Porównania można łączyć używając operatorów boolowskich and i or. Wynik porównania (lub jakiegokolwiek innego wyrażenia boolowskiego) można zanegować używając not. Operatory te mają mniejszy priorytet niż operatory porównania; wśród nich not ma najwyższy priorytet a or najniższy, więc A and not B or C jest ekwiwalentem (A and (not B)) or C. Jak zwykle można użyć nawiasów, aby wyrazić pożądaną kolejność kompozycji wyrażenia.

Argumenty operatorów boolowskich and i or są ewaluowane od lewej do prawej. Ewaluacja kończy się w momencie ustalenia wyniku. Na przykład, jeśli A i C są prawdą, ale B jest fałszem, A and B and C nie zewaluuje wyrażenia C. Przy użyciu ogólnej wartości, nie jako boolean, wartość zwracana tych operatorów to ostatnio ewaluowany argument.

Da się przypisać wynik porównania lub inne wyrażenie boolowskie do zmiennej. Na przykład,

>>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
>>> non_null = string1 or string2 or string3
>>> non_null
'Trondheim'

Note that in Python, unlike C, assignment cannot occur inside expressions. C programmers may grumble about this, but it avoids a common class of problems encountered in C programs: typing = in an expression when == was intended.

5.8. Porównywanie sekwencji i innych typów

Sequence objects may be compared to other objects with the same sequence type. The comparison uses lexicographical ordering: first the first two items are compared, and if they differ this determines the outcome of the comparison; if they are equal, the next two items are compared, and so on, until either sequence is exhausted. If two items to be compared are themselves sequences of the same type, the lexicographical comparison is carried out recursively. If all items of two sequences compare equal, the sequences are considered equal. If one sequence is an initial sub-sequence of the other, the shorter sequence is the smaller (lesser) one. Lexicographical ordering for strings uses the Unicode code point number to order individual characters. Some examples of comparisons between sequences of the same type:

(1, 2, 3)              < (1, 2, 4)
[1, 2, 3]              < [1, 2, 4]
'ABC' < 'C' < 'Pascal' < 'Python'
(1, 2, 3, 4)           < (1, 2, 4)
(1, 2)                 < (1, 2, -1)
(1, 2, 3)             == (1.0, 2.0, 3.0)
(1, 2, ('aa', 'ab'))   < (1, 2, ('abc', 'a'), 4)

Zwróć uwagę, że porównywanie obiektów innych typów przy użyciu < lub > jest dozwolone pod warunkiem, że te obiekty mają odpowiednie metody porównań. Na przykład mieszane typy numeryczne są porównywane w oparciu o ich wartość numeryczną, tak że 0 równa się 0.0 i tak dalej. W innych przypadkach, zamiast zwracać arbitralny porządek, interpreter zgłosi wyjątek TypeError.

Przypisy

1

Inne języki mogą zwracać zmieniony obiekt, co pozwala na łańcuchowanie metod, na przykład d->insert("a")->remove("b")->sort();.

2

Calling d.keys() will return a dictionary view object. It supports operations like membership test and iteration, but its contents are not independent of the original dictionary – it is only a view.