Moduł collections#

Opis modułu#

Moduł collections zawiera wyspecjalizowane typy danych. Są one alternatywą dla wbudowanych typów danych takich jak dict, list, set i tuple.

Nazwa typu

Opis

namedtuple()

Funkcja służąca do tworzenia krotek z nazwanymi polami.

deque

Kontener podobny do listy, z dostępem zarówno od początku, jak i od końca.

Counter

Klasa słownika służąca do liczenia obiektów.

OrderedDict

Klasa słownika, która pamięta kolejność, w jakiej dodawano elementy.

defaultdict

Klasa słownika, która umożliwia zdefiniowanie brakujących elementów.

namedtuple#

Nazwane krotki są stosowane wszędzie tam, gdzie potrzebujemy manipulować niewielkimi rekordami, a jednocześnie nie chcemy tworzyć osobnej klasy.

Przykładem jest klasa Point:

from collections import namedtuple
import math

Point = namedtuple('Point', 'x y z')

def distance(a, b):
    return math.sqrt(
        (a.x - b.x)**2 +
        (a.y - b.y)**2 +
        (a.z - b.z)**2
    )
a = Point(x=1, y=2, z=3)
b = Point(-1, -2, 42)
print(distance(a, b))
39.25557285278104

Nazwane krotki przydają się także wtedy, gdy chcemy zwrócić więcej niż jeden obiekt. Funkcja lub metoda może zwrócić kilka obiektów w krotce, ale trzeba wówczas pamiętać, w jakiej kolejności są one zwracane. Alternatywą jest zwrócenie nazwanej krotki:

from collections import namedtuple

Result = namedtuple('Result', 'quotient remainder')

def div_mod(a, b):
    return Result(quotient=a//b, remainder=a%b)
r = div_mod(9, 2)
r
Result(quotient=4, remainder=1)
r.quotient
4
r.remainder
1

Deque#

W niektórych algorytmach stosowane są kolejki FIFO (first in, first out). Kolejka jest listą, której nowe elementy są dodawane na jej początku. Z kolei inny wątek może pobierać elementy (odczytywać je i usuwać z listy) z jej końca.

Stosowanie listy może spowolnić program, ponieważ dodanie lub usunięcie elementu z początku listy jest operacją kosztowną (złożoność O(n)). Dlatego w takich sytuacjach stosuje się wyspecjalizowany kontener deque. Jego zaletą jest możliwość szybkiego (w czasie stałym O(1)) dodawania i usuwania elementów zarówno z początku, jak i końca.

[deque]{.title-ref} udostępnia ten sam interfejs, co listy. Dostępne są również metody appendleft, extendleft oraz popleft, które działają tak samo jak odpowiednio append, extend i pop, ale działają na początku kolekcji zamiast na jej końcu.

Na przykład, appendleft(x) wstawia obiekt x na początek kolejki. Jest to równoważne wywołaniu insert(0, x)).

import collections

dq = collections.deque('abcdefg')
dq
deque(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
len(dq)
7
dq[0]
'a'
dq[-1]
'g'
dq.remove('c')
dq
deque(['a', 'b', 'd', 'e', 'f', 'g'])
while True:
    try:
        print(dq.pop())
    except IndexError:
        break
g
f
e
d
b
a
dq = collections.deque('abcdefg')

while True:
    try:
        print(dq.popleft())
    except IndexError:
        break
a
b
c
d
e
f
g
dq = collections.deque(range(10))
dq
deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
dq = collections.deque(range(10))
dq.rotate(2)
dq
deque([8, 9, 0, 1, 2, 3, 4, 5, 6, 7])
dq = collections.deque(range(10))
dq.rotate(-2)
dq
deque([2, 3, 4, 5, 6, 7, 8, 9, 0, 1])

Counter#

Counter jest klasą ułatwiającą zliczanie elementów. Jest to słownik, którego kluczami są elementy, a wartościami częstość ich występowania. Przy tworzeniu instancji należy przekazać kolekcję elementów, które mają zostać zliczone.

Zliczanie znaków występujących w danym napisie można wykonać przy pomocy zwykłego słownika:

counter = {}

for char in "Hello there, People!":
    if char not in counter:
        counter[char] = 1
    else:
        counter[char] += 1

print(counter)
{'H': 1, 'e': 5, 'l': 3, 'o': 2, ' ': 2, 't': 1, 'h': 1, 'r': 1, ',': 1, 'P': 1, 'p': 1, '!': 1}

Lub szybciej, przy pomocy klasy Counter:

from collections import Counter

counter = Counter("Hello there, People!")

print(counter)
Counter({'e': 5, 'l': 3, 'o': 2, ' ': 2, 'H': 1, 't': 1, 'h': 1, 'r': 1, ',': 1, 'P': 1, 'p': 1, '!': 1})

most_common jest metodą umożliwiającą sprawdzenie, które elementy występują najczęściej. Jeśli chcemy znaleźć najczęściej występujące słowa w pliku holmes.txt, możemy wykonać:

import re

words = re.findall('\w+', open('holmes.txt').read().lower())

print(Counter(words).most_common(10))
[('the', 5810), ('and', 3088), ('i', 3038), ('to', 2823), ('of', 2778), ('a', 2700), ('in', 1823), ('that', 1767), ('it', 1749), ('you', 1572)]

OrderedDict#

Słowniki przechowują pary (klucz, wartość), ale nie pamiętają kolejności, w jakiej poszczególne pary zostały dodane. OrderedDict jest słownikiem, który zapamiętuje tę kolejność. Podczas iterowania po nim, zwraca on klucze w kolejności, w jakiej zostały dodane:

from collections import OrderedDict

d = OrderedDict()
d['c'] = 1
d['b'] = 2
d['a'] = 1

for k in d:
    print(k)
c
b
a

Dodatkowo, ten obiekt udostępnia metodę popitem:

d.popitem()  # zwraca ostanio dodaną parę (klucz, wartość) (kolejka LIFO)
('a', 1)
d.popitem(last=False)  # zwraca pierwszą dodaną parę (kolejka FIFO)
('c', 1)

defaultdict#

defaultdict jest takim słownikiem, w którym użycie klucza nie będącego w tym słowniku powoduje użycie domyślnej wartości zamiast zgłoszenia wyjątku KeyError.

Ten obiekt pozwala uprościć kod wykorzystujący ideę multisłownika, to znaczy słownika, w którym jeden klucz może mieć więcej niż jedną wartość. Taki multisłownik może zostać zaimplementowany jako zwykły słownik, którego wartościami jest lista.

s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
res = {}

for color, item in s:
    if color in res:
        res[color].append(item)
    else:
        res[color] = [item, ]

res.items()
dict_items([('yellow', [1, 3]), ('blue', [2, 4]), ('red', [1])])

Rozwiązanie z użyciem defaultdict jest znacznie prostsze. Przy tworzeniu słownika przekazujemy funkcję, która generuje wartość domyślną. W tym przypadku wartością domyślną jest pusta lista, którą generuje wbudowana funkcja list.

from collections import defaultdict

s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]

d = defaultdict(list)
for k, v in s:
    d[k].append(v)

print(d)
defaultdict(<class 'list'>, {'yellow': [1, 3], 'blue': [2, 4], 'red': [1]})