<jupyter_start><jupyter_text>Лекция 8 Продвинутые коллекции * frozenset – суть, основные методы, пример использованияМодуль collections* Counter – суть, основные методы, пример использования* defaultdict – суть, основные методы, несколько примеров использования (можно из документации)* OrderedDict –  суть, основные методы, несколько примеров использования (нужны хорошие!)* namedtuple() – суть, основные методы,  несколько примеров использования (нужны хорошие!); про преимущества и недостатки по сравнению со словарем; (классом?); кортежемМодуль enum:https://docs.python.org/3/library/enum.htmlhttps://pymotw.com/3/enum/  Frozen set Frozen set - "замороженное множество". Отличается от обычного множества set тем, что не может быть изменено после создания, а также является хэшируемым, то есть может служить ключом в словаре или входить в другое множество.Напоминание: Обычное множество set - неупорядоченный набор различных хэшируемых элементов. Поддерживает математические операции объединения, пересечения, разности и др. Так как множество не упорядочено, то над ним недоступны операции индексирования и получения срезов.<jupyter_code>a = set('qwerty') # создание обычного множества
a
b = frozenset('qwerty') # создание "замороженного множества"
b
# сравнение множеств любого типа идет поэлементно: 
# если элементы в обоих множествах одинаковые, 
# то множества считаются равными (даже если они разных типов set и frozenset)
a == b 
a-b
# операции над множествами могут применяться над множествами любых типов
type(a - b) # разность множеств set и frozenset - тип set!
type(a | b) # объединение множеств set и frozenset - тип set!
# если операция выполняется между множествами frozenset, то результат - тоже frozenset
type(frozenset(a) & b) 
a.add(1) # в set можно добавлять элементы
a
b.add(1) # в frozenset добавлять элементы нельзя!<jupyter_output><empty_output><jupyter_text>Модуль collections Модуль collections содержит специализированные типы контейнеров данных, которые можно использовать в качестве альтернативы контейнерам общего назначения Python (dict, tuple, list и set). https://docs.python.org/3.3/library/collections.html<jupyter_code>import collections<jupyter_output><empty_output><jupyter_text>collections.Counter([iterable-or-mapping]) Counter ("счетчик") - вид словаря, который предназначен для подсчета количества хэшируемых объектов. Ключами счетчика являются хэшируемые объекты (в частном случае, неизменяемые, такие как примитивные типы данных, кортежи, frozenset), а значениями - их количество. Причем количество может быть любым целым числом (в т.ч. отрицательным).Работа с Counter аналогична работе с обычным словарем dict, с отличиями:* При создании очередного счетчика на вход конструктору можно передать итерируемый объект из хэшируемых элементов (например, список из чисел или строку), в случае чего счетчик сам рассчитывает количество разных элементов в этом объекте.* При попытке получить значение ключа, который отсутствует в счетчике, возвращается 0 (а не KeyError, как для обычных словарей).* Имеет несколько специфичных функций (методов).<jupyter_code>set(list("ababababfdscabacs"))
c = collections.Counter() # новый пустой счетчик
c
c = collections.Counter("ababababfdscabacs") # новый счетчик из итерируемого объекта
c
c['x']
c['a']
d1 = dict(c)
d1
print(d1.get('x'))
c = collections.Counter(['aaa', 4, 'bbb', 'bbb', 4, 2, 4]) # новый счетчик из итерируемого объекта
c
c = collections.Counter({'red': 4, 'blue': 2})      # a new counter from a mapping
c
c = collections.Counter(cats=4, dogs='dd')             # a new counter from keyword args
c
c.update(['cats'])
c['cats'] += 3
c
c['cows']<jupyter_output><empty_output><jupyter_text>Методы collections.Counter<jupyter_code>from collections import Counter
Counter('aabbcc')
cntr('aaaabb')<jupyter_output><empty_output><jupyter_text>elements() - возвращает список из элементов счетчика, количество повторений которых соответствует их значениям в счетчике, в произвольном порядке. Если количество в счетчике указано меньше 1, то это значение в результирующий список не выводится.<jupyter_code>c = collections.Counter(a=4, b=3, c=0, d=-2, e=-15, g=3)
list(c.elements())
for e in c.elements():
    print(e)<jupyter_output>a
a
a
a
b
b
b
g
g
g<jupyter_text>most_common([n]) - Возвращает список из n наиболее часто встречаемых элементов и их количество в порядке уменьшения частоты появления. Если количество одинаковое, то порядок произвольный. Если n не указано, то возвращает все элементы счетчика (тоже в порядке уменьшения количества).<jupyter_code>for e, qty in c.most_common(3):
    print(e)<jupyter_output>a
b
g<jupyter_text>subtract([iterable-or-mapping]) - из значений счетчика вычитаются элементы другого итерируемого объекта или сопоставления. Аналогично dict.update(), но вычитает количество вместо замены значений. И входные, и выходные значения могут быть нулевыми или отрицательными.<jupyter_code>c = collections.Counter(a=4, b=2, c=0, d=-2, x=11)
d = collections.Counter(a=1, b=2, c=3, d=4, g=-5, h=0)
c.subtract(d)
c
c = collections.Counter(a=4, b=2, c=0, d=-2)
d = collections.Counter()
d.subtract(c)
d
c = collections.Counter(a=4, b=2, c=0, d=-2)
# d = collections.Counter()
c.subtract('aabdddc')
c<jupyter_output><empty_output><jupyter_text>update([iterable-or-mapping]) - функция обычного словаря, которая работает иначе для счетчиков: значения счетчика складываются со значениями другого итерируемомо объекта или сопоставления. Аналогично dict.update(), но суммирует количество вместо замены значений. И входные, и выходные значения могут быть нулевыми или отрицательными.<jupyter_code>c = collections.Counter(a=4, b=2, c=0, d=-2)
d = collections.Counter(a=1, b=2, c=0, d=4, g=-5)
c.update(d)
c
c = collections.Counter(a=4, b=2, c=0, d=-2)
# d = collections.Counter()
c.update(['x','x','x'])
# c.update('x')
# c.update('x')
c<jupyter_output><empty_output><jupyter_text>При работе со счетчиками доступны математические операции для их комбинирования для создания "мультимножеств" (счетчиков только с количествами больше 0). Сложение и вычитание счетчиков складывает или вычитает соответствующие значения, пересечение и объединение возвращают минимальные или максимальные количества, соответственно.<jupyter_code>c = collections.Counter(a=4, b=2, c=0, d=-2)
d = collections.Counter(a=1, b=2, c=0, d=4, g=-5)
c + d
c - d
c & d
c | d
+c # операция, позволяющая вернуть счетчик без учета количеств меньше 1 (осуществляет сложение с пустым словарем)
-c # операция, позволяющая вернуть счетчик с инвертированными количествами (осуществляет вычитание из пустого словаря)<jupyter_output><empty_output><jupyter_text>collections.defaultdict([default_factory, ]...) defaultdict - наследуемый класс Python dict, который принимает default_factory как первичные аргументы. Тип default_factory — это обычный тип Python, такой как int или list, но вы также можете использовать функцию или лямбду. Тип данных, который практически в точности повторяет функциональные возможности словарей, за исключением способа обработки обращений к несуществующим ключам.Когда происходит обращение к несуществующему ключу, вызывается функция, которая передается в аргументе default_factory. Эта функция должна вернуть значение по умолчанию, которое затем сохраняется как значение указанного ключа.Остальные аргументы функции defaultdict() в точности те же самые, что передаются встроенной функции dict().Объекты типа defaultdict удобно использовать в качестве словаря для слежения за данными.Например, предположим, что необходимо отслеживать позицию каждого слова в строке s. Ниже показано, насколько просто это можно реализовать с помощью объекта defaultdict: Пример: Счетчик слов в тексте<jupyter_code>#Без defaultdict

sentence = "The red for jumped over the fence and ran to the zoo for food"
words = sentence.split(' ')
 
reg_dict = {}
for ind, word in enumerate(words):
    if word in reg_dict:
        reg_dict[word] += [ind]
    else:
        reg_dict[word] = [ind]
reg_dict
# генератор значений по умолчанию:
list()
#С использованием defaultdict
from collections import defaultdict
 
sentence = "The red for jumped over the fence and ran to the zoo for food"
words = sentence.split(' ')
 
d = defaultdict(list)
for ind, word in enumerate(words):
    d[word] += [ind]
d
sentence = "The red for jumped over the fence and ran to the zoo for food"
words = sentence.split(' ')
 
d = defaultdict(list)
for ind, word in enumerate(words):
    d[word].append(ind)
d<jupyter_output><empty_output><jupyter_text>collections.OrderedDict([items]) Возвращает экземпляр подкласса dict, поддерживающий обычные методы dict. OrderedDict - это упорядоченный словарь, то есть dict, который помнит порядок, в котором были вставлены ключи. Если новая запись перезаписывает существующую запись, исходная позиция вставки в словарь остается неизменной. При удалении записи и повторном ее добавлении в OrderedDict она переместится в конец словаря. Могут использоваться в коде абсолютно аналогично обычным словарям.<jupyter_code>from collections import OrderedDict
d = OrderedDict()
d
d = OrderedDict(x=2, z=3, v=4)
d
d1 = {'a': 5, 'x': 3, 'm': 6}
d = OrderedDict(d1)
d
for k, v in d1.items():
    print(k, v)
d['x']
d['x'] = 5
d
del d['x']
d
d['x'] = 7
d<jupyter_output><empty_output><jupyter_text>Методы OrderedDict popitem(last=True) - возвращает как результат и удаляет из упорядоченного словаря последний элемент, если last=True, и первый, если last=False.<jupyter_code>print(d.popitem())
d
d['f'] = 11
d.popitem()
print(d.popitem(last=False))
d<jupyter_output>('a', 5)<jupyter_text>move_to_end(key, last=True) - перемещает указанный ключ в конец упорядоченного словаря, если last=True, и в начало, если last=False. Возвращает ошибку KeyError, если указанного ключа в словаре нет.<jupyter_code>d = OrderedDict.fromkeys('abcde', 0)
d
for k, v in d.items():
    print(k, v)
d.move_to_end('b')
' '.join(d.keys())
d.move_to_end('d', last=False)
' '.join(d.keys())
d.move_to_end('x')<jupyter_output><empty_output><jupyter_text>collections.namedtuple(typename, field_names, verbose=False, rename=False)<jupyter_code>NAME = 0
VALUE = 1
c1 = ('Masha', 42)
c1[0]
c1[NAME]<jupyter_output><empty_output><jupyter_text>Возвращает новый класс (подкласс) кортежей: именованный кортеж. Аналогичен обычному кортежу, но позволяет обращаться к элементам по названию поля, а не только по индексу. Таким образом, именованный кортеж наделяет позицию в кортеже дополнительным смыслом и делает код прозрачнее (самодокументируемым). При этом по производительности ничем не отличаются от обычных кортежей (не являются более медленными и тяжелыми).typename - название нового подкласса кортежей. Теоретичесски может не совпадать с названием переменной, используемой для создания кортежей этого подкласса (то есть это не одно и то же).fieldnames - имена полей кортежа в виде строки (где названия полей идут через запятую или пробел) либо в виде списка строк (где каждая строка - название поля). Имена должны быть допустимыми идентификаторами Python. Порядок их следования определяет порядок следования элементов кортежа.rename - если True, то некорректные имена полей из fieldnames при создании нового класса именованных кортежей заменяются на имена по умолчанию (нижнее подчеркивание + индекс поля, например "\_2").verbose - устаревший параметр. Если True, то на печать выводится определение нового класса. Вместо этой опции лучше выводить параметр \_source.<jupyter_code>from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(11, 22)     # создание нового объекта с указанием позиционного параметра и по ключевому слову
p
p.x
p.y
p2 = Point(y=7, x=11)
p2
p2.x = 8
p[0] + p[1]
a, b = p # стандартная распаковка кортежа
a, b
p.x + p.y # обращение к элементам кортежа по имени поля
Point2 = namedtuple('Point2', 'x y z')
d = {'x': 11, 'y': 22, 'z':0}
Point2(**d)
s = namedtuple('MyPoint', ['x', 'y']) # название класса в документации может отличаться, но это не приветствуется
s(x=11, y=22)
Point = namedtuple('Point', ['x', '2y', 'z', 'x', 'def'])
Point = namedtuple('Point', ['x', '2y', 'z', 'x', 'def'], rename=True)
Point._fields
# еще пример
NetworkAddress = namedtuple('NetworkAddress', ['hostname', 'port'])
a = NetworkAddress('www.python.org', 80)
a.hostname
a.port
type(a)==tuple
isinstance(a, tuple)<jupyter_output><empty_output><jupyter_text>С помощью namedtuple очень удобно считывать данные, например, из csv-файла, чтобы потом обращаться к элементам строк по имени поля (а не по индексу).<jupyter_code>import csv
with open("employees.csv", "r") as f:
    f_csv = csv.reader(f)
    # создаем новый класс именованного кортежа, названия полей которого отражают поля из входного файла csv:
    EmployeeRecord = namedtuple('EmployeeRecord', next(f_csv)) 
    for line in f_csv: # все остальные строки из входного файла считываем в виде кортежа EmployeeRecord
        empl = EmployeeRecord._make(line)
        print(empl.name, empl.title)<jupyter_output>Ivan Manager
Maria Accountant
Victor Programmer<jupyter_text>Методы Помимо стандартных методов кортежей, именованным кортежам доступно еще несколько специализированных методов и атрибутов, названия которых начинаются с нижнего подчеркивания, чтобы избежать потенциальных конфликтов с названиями полей данного именованного кортежа.  somenamedtuple._make(iterable) Создает новый экземпляр данного именованного кортежа из существующей последовательности или итерируемого объекта.<jupyter_code>Point = namedtuple('Point', ['x', 'y'])
t = [11, 22]
Point._make(t)<jupyter_output><empty_output><jupyter_text>somenamedtuple._asdict() Возвращает новый OrderedDict, который сопоставляет имена полей кортежа с их значениями.<jupyter_code>p = Point(x=11, y=22)
d1 = p._asdict()
for k, v in d1.items():
    print(k, v)<jupyter_output>x 11
y 22<jupyter_text>somenamedtuple._replace(**kwargs) Возвращает новый именованный кортеж, у которого значения указанных в качестве аргумента полей заменены на заданные значения.<jupyter_code>p = Point(x=11, y=22)
p2 = p._replace(x=33)
p2
p is p2<jupyter_output><empty_output><jupyter_text>Метод _replace можно использовать для кастомизации заданного прототипа (кортежа со значениями по умолчанию):<jupyter_code>Account = namedtuple('Account', 'owner balance transaction_count')
default_account = Account('<owner name>', 0.0, 0)
johns_account = default_account._replace(owner='John')
janes_account = default_account._replace(owner='Jane')<jupyter_output><empty_output><jupyter_text>somenamedtuple._source  Возвращает код определения данного класса именованного кортежа (в виде текстовой строки).<jupyter_code>Point._source<jupyter_output><empty_output><jupyter_text>somenamedtuple._fields Возвращает кортеж строк с именами полей именованного кортежа. Полезен для создания новых типов именованных кортежей из уже существующих.<jupyter_code>p._fields 
Color = namedtuple('Color', 'red green blue')
Pixel = namedtuple('Pixel', Point._fields + Color._fields)
px1 = Pixel(11, 22, 128, 255, 0)
px1
px2 = Pixel(x=11, y=22, red=128, green=255, blue=0)
px2<jupyter_output><empty_output><jupyter_text>Чтобы получить значения полей, чьи имена сохранены как строки, можно использовать функцию getattr():<jupyter_code>getattr(p, 'x')
getattr(px1, 'red')
fields = ['red', 'y', 'x']
for field in fields:
    print(getattr(px2, field))<jupyter_output>128
22
11<jupyter_text>Сравнение namedtuple с другими типами данных Кортежи:* namedtuple позволяет обращаться не только по индексу, но и по именам полей.* И tuple, и namedtuple по умолчанию не изменяемы, но в namedtuple можно изменять значения с помощью функции replace().* При этом namedtuple занимает ровно столько же памяти, сколько и обычный кортеж (вся дополнительная информация хранится в определении класса). Словари:* В словарях ключами могут быть только хэшируемые объекты, в именованных кортежах названиями полей - только строки (еще более узко).* Значениями и в словарях, и в именованных кортежах могут быть любые объекты.* Названия полей в namedtuple упорядочены (причем в порядке, заданном пользователем), а ключи в словаре - нет.* Значения в словарях легко изменяемы, а namedtuple по умолчанию считается неизменяемым объектом (бОльшая защищенность данных).* При создании словарей нужно каждый раз указывать все поля. Если же требуется создать несколько объектов, у которых названия полей одни и те же, то чтобы их каждый раз не писать, можно один раз описать их в новом классе именованного кортежа.* Именованный кортеж занимает меньше памяти, так как не требует хранения названий полей для каждого экземпляра кортежа (в отличие от словарей). Классы:* Именованные кортежи могут заменить определение новых классов, если в них есть только фиксированный набор обычно не изменяемых параметров.  Модуль enum Перечисление представляет собой набор символических имен (членов), связанных с уникальными постоянными значениями. В пределах перечисления члены могут сравниваться по идентификатору, и само перечисление может быть повторено.  Содержание модуля Этот модуль определяет четыре класса перечислений, которые могут использоваться для определения уникальных наборов имен и значений: Enum , IntEnum , Flag и IntFlag . Он также определяет один декоратор, unique() и один помощник, auto  enum.Enum Базовый класс для создания перечислимых констант. enum.IntEnum Базовый класс для создания перечислимых констант, которые также являются подклассами int . enum.IntFlagБазовый класс для создания перечислимых констант, которые можно комбинировать с помощью побитовых операторов, не теряя членства в IntFlag . Члены IntFlag также являются подклассами int . enum. Flag Базовый класс для создания перечислимых констант, которые можно комбинировать с помощью побитовых операций, не теряя членства в Flag  enum. unique ( ) Декоратор класса Enum, который обеспечивает только одно имя, привязан к какому-либо одному значению. enum.auto Экземпляры заменяются соответствующим значением для членов Enum.<jupyter_code>from enum import Enum
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3<jupyter_output><empty_output><jupyter_text>Значениями элементов могут быть любые: int , str и т. Д. Если точное значение неважно, вы можете использовать auto экземпляры, и для вас будет выбрано соответствующее значение. Класс Color - это перечисление (или перечисление ) Атрибуты Color.RED , Color.GREEN и т. Д. Являются членами перечисления (или перечисляющими членами ) и являются функциональными константами. Члены перечисления имеют имена и значения (имя Color.RED равно RED , значение Color.BLUE равно 3 и т. Д.). Несмотря на то, что мы используем синтаксис class для создания Enums, Enums не являются нормальными классами Python.<jupyter_code>print(Color.RED)
print(repr(Color.RED)) 
#repr Для многих типов функция возвращает строку, которая при передаче в eval() может произвести объект с тем же значением, 
#что и исходный.
#В других случаях представление является строкой, обрамлённой угловыми скобками (< и >), 
#содержащей название типа и некую дополнительную информацию, часто — название объекта и его адрес в памяти. 
type(Color.RED)

isinstance(Color.GREEN, Color)<jupyter_output><empty_output><jupyter_text>Перечисления поддерживают итерацию в порядке определения:<jupyter_code>class Shake(Enum):
    VANILLA = 7
    CHOCOLATE = 4
    COOKIES = 9
    MINT = 3
for shake in Shake:
    print(shake)
#Члены перечисления хешируются, поэтому их можно использовать в словарях и множествах

apples = {}
apples[Color.RED] = 'red delicious'
apples[Color.GREEN] = 'granny smith'
apples == {Color.RED: 'red delicious', Color.GREEN: 'granny smith'}<jupyter_output><empty_output><jupyter_text>Программный доступ к элементам перечисления и их атрибутам  Иногда полезно обращаться к членам в перечислениях программно (т.е. ситуации, когда Color.RED не будет выполняться, потому что точный цвет не известен во время записи программы). Enum допускает такой доступ<jupyter_code>Color(1)<jupyter_output><empty_output><jupyter_text>Если вы хотите получить доступ к элементам перечисления по имени , используйте доступ к элементу:<jupyter_code>Color['RED']<jupyter_output><empty_output><jupyter_text>Если у вас есть член перечисления и необходимо его name или value<jupyter_code>member = Color.RED
member.name, member.value<jupyter_output><empty_output><jupyter_text>Дублирование двух членов перечисления запрещено, будет возникать ошибка TypeError: Attempted to reuse key:<jupyter_code>class Shape ( Enum ):
    SQUARE = 2
    SQUARE = 3<jupyter_output><empty_output><jupyter_text>Однако двум членам перечисления разрешено иметь одинаковое значение. Учитывая два члена A и B с тем же значением (и A, определенным вначале), B является псевдонимом A. Поиск по значению значения A и B вернет A. Поиск по имени B также вернет A<jupyter_code>class Shape(Enum):
    SQUARE = 2
    DIAMOND = 1
    CIRCLE = 3
    ALIAS_FOR_SQUARE = 2
Shape.SQUARE
Shape.ALIAS_FOR_SQUARE
Shape(2)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08562529947292764, 'fraction_numerical': 0.00967896502156205, 'mean_word_length': 6.01074907625126, 'pattern_counts': {'":': 0, '<': 121, '<?xml version=': 0, '>': 121, 'https://': 3, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '31', 'dir': 'jupyter-structured-clean-dedup', 'id': '341466', 'license': 'WTFPL', 'n_tokens_mistral': 8164, 'n_tokens_neox': 9371, 'n_words': 2309, 'path': 'Course I/Алгоритмы Python/Part1/лекции/jupyter/lec08_advanced_collections/lec8_v2018s2_v1.ipynb', 'repo_name': 'GeorgiyDemo/FA', 'size': '20870'}