Статьи

Коллекции Python: высокопроизводительные контейнеры для сложных задач

1. Введение

Python известен своими мощными встроенными типами данных общего назначения, такими как list , dict , tuple и set., Но в Python также есть объекты коллекций, такие как Java и C ++. Эти объекты разрабатываются поверх общих встроенных контейнеров с дополнительными функциями, которые можно использовать в специальных сценариях. Цель этой статьи — представить объекты коллекции Python и объяснить их с помощью соответствующих фрагментов кода. Библиотека коллекций содержит объекты коллекций: именованные кортежи (v2.6), deque (v2.4), ChainMap (v3.3), Counter (v2.7), OrderedDict (v2.7) и defaultdict (v2. 5). В Python 3.x также есть userDict, userList и userString для создания пользовательских типов контейнеров (не в рамках этой статьи), что заслуживает отдельной статьи. ПРИМЕЧАНИЕ. Пользователь Python 2.x может знать о различных выпусках, которые они представляют. Все эти объекты доступны в Python 3.x начиная с версии 3.1, за исключением ChainMap, который был представлен в версии 3.3.Все фрагменты кода в статьях выполняются в среде Python 3.5.

2. Namedtuple

Как следует из названия, namedtuple — это кортеж с именем. В стандартном кортеже мы получаем доступ к элементам, используя индекс, тогда как namedtuple позволяет пользователю определять имя для элементов. Это очень удобно, особенно при обработке файлов CSV (значения, разделенные запятыми) и работе со сложными и большими наборами данных, когда код становится беспорядочным с использованием индексов (не так Pythonic).

2.1 Пример 1

Именованные кортежи доступны в библиотеке коллекций в Python. Мы должны импортировать библиотеку коллекций перед использованием любых контейнерных объектов из этой библиотеки.

>>>from collections import namedtuple
>>>saleRecord = namedtuple('saleRecord','shopId saleDate salesAmout totalCustomers')
>>>
>>>
>>>#Assign values to a named tuple 
>>>shop11=saleRecord(11,'2015-01-01',2300,150) 
>>>shop12=saleRecord(shopId=22,saleDate="2015-01-01",saleAmout=1512,totalCustomers=125)

В приведенном выше фрагменте кода в первой строке мы импортируем namedtuple из библиотеки коллекций. Во второй строке мы создаем именованный кортеж с именем saleRecord, в котором полями являются shopId, saleDate, salesAmount и totalCustomers. Обратите внимание, что namedtuple () принимает два строковых аргумента: первый аргумент — это имя кортежа, а второй аргумент — это список имен полей, разделенных пробелом или запятой . В приведенном выше примере пробел используется как разделитель. Мы также создали два кортежа здесь. Это магазин11 и магазин12. Для shop11 значения присваиваются полям на основе порядка полей, а shop12 значения присваиваются с использованием имен .

2.2 Пример 2

>>>#Reading as a namedtuple
>>>print("Shop Id =",shop12.shopId)
12
>>>print("Sale Date=",shop12.saleDate)
2015-01-01
>>>print("Sales Amount =",shop12.salesAmount)
1512
>>>print("Total Customers =",shop12.totalCustomers)
125

Приведенный выше код довольно ясно показывает, что доступ к кортежу осуществляется с использованием имен. Также возможно получить к ним доступ, используя индексы кортежей, что является обычным способом.

2.3 Интересные методы и члены

2.3.1 _make

Метод _make используется для преобразования данного итерируемого элемента (списка, кортежа, словаря) в именованный кортеж.

>>>#Convert a list into a namedtuple
>>>aList = [101,"2015-01-02",1250,199]
>>>shop101 = saleRecord._make(aList)
>>>print(shop101)
saleRecord(shopId=101, saleDate='2015-01-02', salesAmount=1250, totalCustomers=199)

>>>#Convert a tuple into a namedtuple
>>>aTup =(108,"2015-02-28",1990,189)
>>>shop108=saleRecord._make(aTup)
>>>print(shop108)
saleRecord(shopId=108, saleDate='2015-02-28', salesAmount=1990, totalCustomers=189)
>>>

2.3.2 _поля

_Fields — это кортеж, который содержит имена кортежа.

>>>print(sho108._fields)
>>>('shopId', 'saleDate', 'salesAmount', 'totalCustomers')

2.4 Обработка файлов CSV

Как мы уже говорили, namedtuple будет очень удобен при обработке файла данных CSV, где мы можем получить доступ к данным, используя имена вместо индексов, что делает код более значимым и эффективным.

from csv import reader
from collections import namedtuple

saleRecord = namedtuple('saleRecord','shopId saleDate totalSales totalCustomers')
fileHandle = open("salesRecord.csv","r")
csvFieldsList=csv.reader(fileHandle)
for fieldsList in csvFieldsList:
    shopRec = saleRecord._make(fieldsList)
    overAllSales += shopRec.totalSales;

print("Total Sales of The Retail Chain =",overAllSales)

В приведенном выше фрагменте кода у нас есть salesRecord.csv, который содержит записи о продажах магазинов определенной розничной сети. Он содержит значения для полей shopId, saleDate, totalSales, totalCustomers. Поля отделяются запятыми, а записи — новыми строками. Csv.reader () читает файл и предоставляет итератор. Итератор «csvFieldsList» предоставляет список полей для каждой отдельной строки файла CSV. Как мы знаем, _make () преобразует список в namedtuple, а остальная часть кода не требует пояснений. 

3. Счетчик

Счетчик используется для быстрых подсчетов. Это словарь, в котором элементы хранятся в виде ключей, а их значения хранятся в виде значений.

3.1 Создание счетчиков

Класс Counter () принимает итеративный объект в качестве аргумента, вычисляет счетчик для каждого элемента в объекте и представляет его в виде пары ключ-значение.

>>>from collections import Counter
>>>listOfInts=[1,2,3,4,1,2,3,1,2,1]
>>>cnt=Counter(listOfInts)
>>>print(cnt)
Counter({1: 4, 2: 3, 3: 2, 4: 1})

В приведенном выше фрагменте кода listOfInts представляет собой список, который содержит числа. Он передается Counter (), и мы получили cnt, который является контейнерным объектом. CNT представляет собой словарь, который содержит уникальные номера , присутствующие в данном списке в качестве ключей, и их отсчеты уважения как ценности.

3.2 Доступ к счетчикам

Счетчик является подклассом словаря . Таким образом, он может быть доступен так же, как словарь . «Cnt» может быть обработан как обычный объект словаря.

>>> cnt.items()
dict_items([(1, 4), (2, 3), (3, 2), (4, 1)])
>>> cnt.keys()
dict_keys([1, 2, 3, 4])
>>> cnt.values()
dict_values([4, 3, 2, 1])

3.3 Интересные методы и варианты использования

3.3.1 Most_common

Most_common (п) класса счетчика, обеспечивает наиболее часто встречающиеся ключи. В качестве ранга используется n , например, n = 2 предоставит два верхних ключа.

>>>name = "Saravanan Subramanian"
>>>letterCnt=Counter(name)
>>>letterCnt.most_common(1)
[('a', 7)]
>>>letterCnt.most_common(2)
[('a', 7), ('n', 4)]
>>>letterCnt.most_common(3)
[('a', 7), ('n', 4), ('r', 2)]

В приведенном выше коде мы могли видеть, что строка анализируется как независимые символы в качестве ключей, а их соответствующие значения хранятся в виде значений. Таким образом, letterCnt.most_common (1) предоставляет верхнюю букву, которая встречается чаще всего.

3.3.2 Операции на счетчике

Подкласс Counter () также называется Multiset. Он поддерживает операции сложения, вычитания, объединения и пересечения в классе Counter.

>>> a = Counter(x=1,y=2,z=3)
>>> b = Counter(x=2,y=3,z=4)
>>> a+b
Counter({'z': 7, 'y': 5, 'x': 3})
>>> a-b       #This will result in negative values & will be omitted
Counter()    
>>> b-a
Counter({'y': 1, 'x': 1, 'z': 1})
>>> a & b    #Chooses the minimum values from their respective pair
Counter({'z': 3, 'y': 2, 'x': 1})
>>> a | b   #Chooses the maximum values from their respective pair
Counter({'z': 4, 'y': 3, 'x': 2})

4. Словарь по умолчанию

Defaultdict () доступен как часть библиотеки коллекций. Это позволяет пользователю указать функцию, которая будет вызываться, когда ключ отсутствует в словаре. В стандартном словаре доступ к элементу, в котором отсутствует ключ, вызовет ошибку ключа. Таким образом, это проблема при работе с коллекциями (списком, набором и т. Д.), Особенно при их создании. Таким образом, когда к словарю запрашивается ключ, который не существует, функция, передаваемая в качестве аргумента именованному аргументу «default_dictionary» в default_dict (), будет вызываться для установки значения для данного «ключа» в словарь.

4.1 Создание словаря по умолчанию

Defaultdict () доступен как часть библиотеки коллекций. По умолчанию dict принимает функцию без аргумента, которая возвращает значение в качестве аргумента.

4.1.1 Пример 1

>>> 
>>> booksIndex = defaultdict(lambda:'Not Available')
>>> booksIndex['a']='Arts'
>>> booksIndex['b']='Biography'
>>> booksIndex['c']='Computer'
>>> print(booksIndex)
defaultdict(<function  at 0x030EB3D8>, {'c': 'Computer', 'b': 'Biography', 'a': 'Arts'})
>>> booksIndex['z']
'Not Available'
>>> print(booksIndex)
defaultdict(<function  at 0x030EB3D8>, {'c': 'Computer', 'b': 'Biography', 'z': 'Not Available', 'a': 'Arts'})
>>> 

В приведенном выше примере booksIndex является defaultdict, где он устанавливает значение Not Available  в качестве значения при обращении к любому несуществующему ключу. Мы добавили значения для ключей a, b и c в defaultdict . В печати (booksIndex) показывает , что defaultdict содержит значение только для этих ключей. При попытке получить доступ к значению ключа ‘z’, которое мы не установили, оно возвращает значение как недоступное и обновляет словарь.

4.1.2 Пример 2

>>> titleIndex = [('a','Arts'),('b','Biography'),('c','Computer'),('a','Army'),('c','Chemistry'),('d','Dogs')]
>>> rackIndices = defaultdict(list)
>>> for id,title in titleIndex:
rackIndices[id].append(title)
>>> rackIndices.items()
dict_items([('d', ['Dogs']), ('b', ['Biography']), ('a', ['Arts', 'Army']), ('c', ['Computer', 'Chemistry'])])
>>> 

В приведенном выше примере titleIndex содержит список кортежей. Мы хотим объединить этот список кортежей, чтобы определить названия для каждого алфавита. Таким образом, у нас может быть словарь, где ключ — это алфавит, а значение — список заголовков. Здесь мы используем defaultdict со списком как функцию, которая вызывается для отсутствующих элементов. Таким образом, для каждого нового элемента будет вызван список, и он создаст пустой объект списка. Последовательные методы append () в списке будут добавлять элементы в список.

5. Заказанный словарь

Упорядоченный словарь поддерживает порядок элементов, добавляемых в словарь, где стандартный словарь не будет поддерживать порядок включения.

5.1 Упорядоченное создание словаря

Упорядоченный словарь создается с помощью OrderedDict () из библиотеки коллекций. Это подкласс обычного словаря, поэтому он наследует все другие методы и поведение обычного словаря.

>>> from collections import OrderedDict
>>> dOrder=OrderedDict()
>>> dOrder['a']='Alpha'
>>> dOrder['b']='Bravo'
>>> dOrder['c']='Charlie'
>>> dOrder['d']='Delta'
>>> dOrder['e']='Echo'
>>> dOrder
>>> OrderedDict([('a', 'Alpha'), ('b', 'Bravo'), ('c', 'Charlie'), ('d', 'Delta'), ('e', 'Echo')])
>>> >>> dOrder.keys()
odict_keys(['a', 'b', 'c', 'd', 'e'])
>>> dOrder.values()
odict_values(['Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo'])
>>> dOrder.items()
odict_items([('a', 'Alpha'), ('b', 'Bravo'), ('c', 'Charlie'), ('d', 'Delta'), ('e', 'Echo')])
>>> 

5.2 Создание из других итерируемых элементов

OrderedDict также может быть создан путем передачи словаря или списка ключей, пары значений.

>>> from collections import OrderedDict
>>> listKeyVals = [(1,"One"),(2,"Two"),(3,"Three"),(4,"Four"),(5,"Five")]
>>> x = OrderedDict(listKeyVals)
>>> x
OrderedDict([(1, 'One'), (2, 'Two'), (3, 'Three'), (4, 'Four'), (5, 'Five')])
>>> 

5.3 Сортировка и хранение

Одним из интересных вариантов использования OrderedDict является проблема ранга. Например, рассмотрим проблему со словарем, содержащим имена учеников и их оценки, теперь мы должны найти лучшего ученика и расставить его по оценкам. Итак, OrderedDict — правильный выбор здесь. Так как OrderedDict запомнит порядок или дополнение, а sorted () отсортирует словарь, мы можем объединить оба, чтобы создать список рангов на основе оценок учеников. Проверьте пример ниже:

>>> studentMarks={}
>>> studentMarks["Saravanan"]=100
>>> studentMarks["Subhash"]=99
>>> studentMarks["Raju"]=78
>>> studentMarks["Arun"]=85
>>> studentMarks["Hasan"]=67
>>> studentMarks
{'Arun': 85, 'Subhash': 99, 'Raju': 78, 'Hasan': 67, 'Saravanan': 100}
>>> sorted(studentMarks.items(),key=lambda t:t[0])
[('Arun', 85), ('Hasan', 67), ('Raju', 78), ('Saravanan', 100), ('Subhash', 99)]
>>> sorted(studentMarks.items(),key=lambda t:t[1])
[('Hasan', 67), ('Raju', 78), ('Arun', 85), ('Subhash', 99), ('Saravanan', 100)]
>>> sorted(studentMarks.items(), key = lambda t:-t[1])
[('Saravanan', 100), ('Subhash', 99), ('Arun', 85), ('Raju', 78), ('Hasan', 67)]
>>> rankOrder = OrderedDict(sorted(studentMarks.items(), key = lambda t:-t[1]))
>>> rankOrder
OrderedDict([('Saravanan', 100), ('Subhash', 99), ('Arun', 85), ('Raju', 78), ('Hasan', 67)])

В приведенном выше примере studentMarks представляет собой словарь, содержащий имя студента в качестве ключа и его отметку в качестве значения. Он получил отсортирован , используя его значение и передается в OrderedDict и получил хранится в rankOrder . Теперь rankOrder содержит самого высокого отмеченного студента в качестве первой записи, и следующего самого высокого в качестве второй записи, и так далее. Этот порядок сохраняется в этом словаре.

6. Deque

Deque означает двустороннюю очередь и произносится как «колода». Это расширение стандартной структуры данных списка. Стандартный список позволяет пользователю добавлять или расширять элементы только в конце. Но deque позволяет пользователю работать на обоих концах, чтобы пользователь мог реализовать как стеки, так и очереди.

6.1 Создание и выполнение операций на Deque

Deque () доступен в библиотеке коллекций. Он принимает итеративную сущность в качестве аргумента и необязательную максимальную длину. Если установлен maxlen , это гарантирует, что длина deque не превышает размер maxlen .

>>> from collections import deque
>>> aiao = deque([1,2,3,4,5],maxlen=5)
aiao = deque([1,2,3,4,5])
>>> aiao.append(6)
>>> aiao
deque([2, 3, 4, 5, 6], maxlen=5)
>>> aiao.appendleft(1)
>>> aiao
deque([1, 2, 3, 4, 5], maxlen=5)

В приведенном выше примере мы создали деку с maxlen 5, как только мы добавили 6-й элемент справа, он толкнул первый элемент слева. Точно так же он выталкивает последний элемент справа, когда мы добавляем элемент слева.

6.2 Операции справа

Операции справа являются общими для выполнения любых операций в списке. Методы append (), extend () и pop () работают с правой стороны deque ().

>>> aiao.append(6)
>>> aiao
deque([2, 3, 4, 5, 6], maxlen=5)
>>> aiao.extend([7,8,9])
>>> aiao
deque([5, 6, 7, 8, 9], maxlen=5)
>>> aiao.pop()
9

6.3 Операция слева

Особенность выполнения операций слева поддерживается набором методов, таких как appendleft (), exteleft () и popleft ().

>>> aiao = deque([1,2,3,4,5],maxlen=5)
>>> aiao.appendleft(0)
>>> aiao
deque([0, 1, 2, 3, 4], maxlen=5)
>>> aiao.extendleft([-1,-2,-3])
>>> aiao
deque([-3, -2, -1, 0, 1], maxlen=5)
>>> aiao.popleft()
-3

6.4 Пример 2 (без макслена)

Если значение maxlen не установлено, deque не выполняет никаких операций обрезки, чтобы сохранить размер deque.

>>> aiao = deque([1,2,3,4,5])
>>> aiao.appendleft(0)
>>> aiao
deque([0, 1, 2, 3, 4, 5])
>>> aiao.extendleft([-1,-2,-3])
>>> aiao
deque([-3, -2, -1, 0, 1, 2, 3, 4, 5])
>>> 

Из приведенного выше примера, deque aiao продолжает расти для операций добавления и расширения, выполняемых над ним.

7. Цепная карта

ChainMap позволяет объединять несколько словарей в один словарь, чтобы операции могли выполняться с одним логическим объектом. ChainMap () не создает никакого нового словаря, вместо этого он поддерживает ссылки на исходные словари, все операции выполняются только над указанными словарями.

7.1 Создание ChainMap

>>> from collections import ChainMap
>>> x = {'a':'Alpha','b':'Beta','c':'Cat'}
>>> y = { 'c': "Charlie", 'd':"Delta", 'e':"Echo"}
>>> z = ChainMap(x,y)
>>> z
ChainMap({'c': 'Cat', 'b': 'Beta', 'a': 'Alpha'}, {'d': 'Delta', 'c': 'Charlie', 'e': 'Echo'})
>>> list(z.keys())
['b', 'd', 'c', 'e', 'a']
>>> list(z.values())
['Beta', 'Delta', 'Cat', 'Echo', 'Alpha']
>>> list(z.items())
[('b', 'Beta'), ('d', 'Delta'), ('c', 'Cat'), ('e', 'Echo'), ('a', 'Alpha')]

Мы создали ChainMap z из других словарей x & y. ChainMap z ссылается на словари x и y. ChainMap не будет содержать дубликаты ключей, он возвращает текущее значение «Cat» для ключа «c». Так что, в основном, он пропускает второе вхождение того же ключа.

>>> x
{'c': 'Cat', 'b': 'Beta', 'a': 'Alpha'}
>>> y
{'d': 'Delta', 'c': 'Charlie', 'e': 'Echo'}
>>> x.pop('c')
'Cat'
>>> x
{'b': 'Beta', 'a': 'Alpha'}
>>> list(z.keys())
['d', 'c', 'b', 'e', 'a']
>>> list(z.values())
['Delta', 'Charlie', 'Beta', 'Echo', 'Alpha']
>>> list(z.items())
[('d', 'Delta'), ('c', 'Charlie'), ('b', 'Beta'), ('e', 'Echo'), ('a', 'Alpha')]
>>> 

В приведенном выше коде мы удалили ключ ‘c’ из dict x. Теперь ChainMap указывает значение для ключа «c» на «Charlie», которое присутствует в y .

8. Резюме

Мы видели различные типы данных коллекции Python и понимали их на примерах и примерах использования. Официальная документация Python может быть передана для дальнейшего чтения.

9. Ссылки

[1] — Python Wiki — https://docs.Python.org/3.5/library/collections.html