07. Словарь. Множество.


Словари DOCS1 DOCS2

Мы уже успели изучить такие типы данных как строки, кортежи и списки (массивы). Все они имеют такое свойство как "номер", то есть к их элементам можно обратиться по индексу и эта идентификация "номер - элемент" однозначна. Однако, в реальной жизни мы не всегда идентифицируем данные только числами. Например, юзернейм в телеграме, номер группы студентов, маршруты поездов, авиарейсы, и др..

То есть для хранения информации о чем-либо в качестве идентификатора удобно использовать текстовую строку. Самым простым случаем такой идентификации являются пары вида "текст - текст", например, "страна - столица", "слово - его значение" и т.д.

Структура данных, позволяющая идентифицировать ее элементы не по числовому индексу, а по индексу произвольного типа, называется словарем или ассоциативным массивом и в Питоне - dict. Идентификатор в Питоне называется key (ключ), а соответствующий элемент - value (значение).

Очень важно, чтобы ключ был 1) уникален, т.е. в словаре не может быть двух одинаковых ключей с разным значением 2) неизменяемого типа, т.е. список не может быть ключом, а кортеж может. В качестве значения может выступать любой тип данных, в том числе другой словарь.

Создание словаря

Словарь можно создать с помощью функции dict() из набора пар ключ - значение и им может быть:

  1. кортеж/список, элемент которого кортеж/список длины 2
In [ ]:
# список списков
dict([["Russia", "Moscow"], ["Ukraine", "Kiev"], ["USA", "Washington"]]) 
# список кортежей
dict([("Russia", "Moscow"), ("Ukraine", "Kiev"), ("USA", "Washington")])
# кортеж списков
dict((["Russia", "Moscow"], ["Ukraine", "Kiev"], ["USA", "Washington"]))
# кортеж кортежей
dict((("Russia", "Moscow"), ("Ukraine", "Kiev"), ("USA", "Washington"))) 
  1. zip объект с элементами длины 2
In [ ]:
# zip 
dict(zip(["Russia", "Ukraine", "USA"], ["Moscow", "Kiev", "Washington"]))
  1. генератор словаря { key : value for _ in _}
In [ ]:
{x : 0 for x in 'abcdef'}
  1. перечисление пар:
    • внутри фигурных скобок через запятую вида key : value
    • внутри функции dict в виде именованых аргументов (тогда ключом может быть только строка)
In [ ]:
# фигурные скобки 
{'Russia': 'Moscow', 'Ukraine': 'Kiev', 'USA': 'Washington'}
# именованые аргументы
dict(Russia = 'Moscow', Ukraine = 'Kiev', USA = 'Washington')
# смесь
dict({'Russia': 'Moscow', 'Ukraine': 'Kiev'}, USA = 'Washington')

Пустой словарь можно создать при помощи функции dict() или пустой пары фигурных скобок {}.

In [ ]:
dict(), {}

Функции применимые к словарям

In [ ]:
d = {'Russia': 'Moscow', 'Ukraine': 'Kiev', 'USA': 'Washington'}

Так как словарь это итерируемый изменяемый тип данных. С помощью функции list можно получить список его ключей (без значений!) в том порядке, в каком они были добавлены; sorted возвращает отсортированный список ключей; функция len как обычно возвращает число элементов.

In [ ]:
list(d), sorted(d), len(d)

d.copy() - возвращает копию* словаря;

d.clear() - удаляет все элементы словаря

In [ ]:
d2 = d.copy()
d.clear()
d2, d

* копировать словарь так же важно, как копировать список - т.к. это изменяемый тип данных, то присваивая словарь новому имени переменной мы не создаем новый объект, а лишь создаем новую "ссылку" на объект, поэтому, меняя старый или новый объект, будем меняться другой:

In [ ]:
d = {'Russia': 'Moscow', 'Ukraine': 'Kiev', 'USA': 'Washington'}
d2 = d
d.clear()
d2, d

d.update(other) - обновляет (пополняет новыми ключами и заменяет значения уже имеющихся) словарь парами key:value из other. Этот метод изменяет сам объект, а не создает новый, поэтому возвращает None.

In [ ]:
d = {'Russia': 'Moscow', 'Ukraine': 'Kiev', 'USA': 'Washington'}
d.update({'Russia' : 'St Petersburg', 'Romania' : 'Buharest'})
d

Работа с ключами и значениями

Как и в списке, обратиться к элементу или изменить его можно по идентификатору с помощью квадратных скобок. Если указанного ключа в словаре не существует, то Питон ругается ошибкой KeyError.

In [ ]:
d['USA']
In [ ]:
d['Russia'] = 'Moscow'
In [ ]:
d['Finland']

Можно проверить, лежит ли ключ в словаре:

In [ ]:
'Finland' in d, 'USA' in d, 'Finland' not in d, 'USA' not in d

Однако, можно обратиться к значению по ключу и без ошибки. Для этого есть метод get(key, [default=None]), который возвращает значение ключа, если ключ есть в словаре, иначе возвращает значение default, которое по дефолту (ого, тавтология) None.

In [ ]:
d.get('USA'), d.get('Finland'), d.get('Finland', 'Not given')

В отличие от списков, создание нового элемента словаря может быть совершено с помощью тех же квадратных скобок:

In [ ]:
d['Finland'] = 'Helsinki'
d

Есть похожий на get метод, который тоже создает новые элементы, но не явно и в более общем смысле. Метод set(key, [default=None]) возвращает значение ключа, если ключ есть в словаре, иначе создает элемент словаря с этим ключом и значением default (который по дефолту None)

In [ ]:
d.setdefault('USA'), d.setdefault('Poland')
In [ ]:
d

Чтобы удалить элемент словаря можно воспользовать командой del, с помощью которой на самом деле уможно удалить любой объект в Питоне. Однакое, если указанного ключа в словаре нет, то Питон будет ругаться KeyError-ом, как и в случае без del.

In [ ]:
del d['Poland']
# del d['Sweden'] 

Более безопасный способ это использовать метод pop(key, [default]), который похож на метод pop для списка. Если ключ есть в словаре, то метод удаляет элемент и возвращает значения ключа. Есть ключа нет, то возвращается указанное значение default, но если default не указан, то Питон будет ругаться KeyError-ом как и в небезопасном способе.

In [ ]:
d.pop('USA'), d
In [ ]:
d.pop('USA', 'Not found')
In [ ]:
d.pop('USA')

Еще один, но не "детерминированный", способ - popitem(). Он удаляет какой-то элемент и возвращает пару (key, value). Порядок, в котором метод "идет" по элементам, называется LIFO - Last In First Out, т.е. в порядке обратном добавлению элементов. В таком же порядке печатается словарь.

Если словарь пустой, то Питон ругается.

In [ ]:
d
In [ ]:
d.popitem(), d.popitem()
In [ ]:
d

Итерирование по словарю

Мы уже упоминали, что словарь - итерируемый объект, а значит по элементам можно проитерироваться циклом. Причем, так как словарь не дает прямого доступа к значениям элементов, т.е. мы к ним всегда имеем доступ только через ключ, то возвращаться будут только ключи.

Порядок прохода по элементам такой же, в котором были добавлены элементы.

In [ ]:
for x in d:
    print(f'Возващаемый ключ: {x},\tзначение по ключу: {d[x]}')

Помимо этого, есть три метода:

  • items() - итерируемый объект типа dict_items, элементы которого пары (key, value)
  • keys() - итерируемый объект типа dict_keys, элементы которого ключи словаря
  • values() - итерируемый объект типа dict_values, элементы которого значения словаря

Заметьте, что эти объекты не поддерживают индексацию, поэтому например нельзя написать d.items()[1].

In [ ]:
d.items(), d.keys(), d.values(), type(d.items()), type(d.keys()), type(d.values())
In [ ]:
for k, v in d.items():
    print(f'{k}: \t{v}')

Когда нужно использовать словари

Особенностью ассоциативного массива является его динамичность: в него можно добавлять новые элементы с произвольными ключами и удалять уже существующие элементы. При этом размер используемой памяти пропорционален размеру ассоциативного массива. Доступ к элементам ассоциативного массива выполняется хоть и медленнее, чем к обычным массивам, но в целом довольно быстро.

Словари нужно использовать в следующих случаях:

  • Подсчет числа каких-то объектов. В этом случае нужно завести словарь, в котором ключами являются объекты, а значениями — их количество.
  • Хранение каких-либо данных, связанных с объектом. Ключи — объекты, значения — связанные с ними данные. Например, если нужно по названию месяца определить его порядковый номер, то это можно сделать при помощи словаря Num['January'] = 1; Num['February'] = 2; ....
  • Установка соответствия между объектами (например, "родитель—потомок"). Ключ — объект, значение — соответствующий ему объект.
  • Если нужен обычный массив, но масимальное значение индекса элемента очень велико, и при этом будут использоваться не все возможные индексы (так называемый “разреженный массив”), то можно использовать ассоциативный массив для экономии памяти.
In [ ]:
d = {}
text = 'python is my favorite programming language \
        python is very powerful python is less powerful than c++'
for word in text.split():
    if word in d:
        d[word] = d[word] + 1
    else:
        d[word] = 1
d.items()
In [ ]:
d = {}
text = 'python is my favorite programming language \
        python is very powerful python is less powerful than c++'
for word in text.split():
    d[word] = d.setdefault(word, 0) + 1
d.items(), sum(d.values())

Множества DOCS1 DOCS2

Множество в языке Питон — это изменяемая структура данных, эквивалентная множествам в математике. Множество состоит из различных элементов, порядок которых неопределен.

Главная особенность множеств это отсутствие порядка. Элементы множества хранятся не подряд, а при помощи хитрых алгоритмов. Это позволяет выполнять проверку на принадлежность элемента множеству быстрее, чем просто перебирая все элементы (как это происходит в списке, словаре, строках и т.д.).

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

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

Создание множества

Множество задается перечислением всех его элементов в фигурных скобках {}.

In [ ]:
A = {1, 2, 3}
A, type(A)

Пустое множество можно создать только при помощи функции set().

In [ ]:
type(set()), type({})

Если функции set передать в качестве параметра список, строку или кортеж, то она вернёт множество, составленное из элементов списка, строки, кортежа.

In [ ]:
set([1, 2, 3, 4, 2, 3, 1]), set('qwertyqa')

Есть генератор множества, который выглядит как и генератор списка, но скобки должны быть фигурными. Не путйет генератор словаря и множества, для словарей используется знак :, чтобы указать "пару".

In [ ]:
a = {x for x in 'abracadabra' if x not in 'ac'}
a, type(a)

Каждый элемент может входить в множество только один раз, порядок задания элементов неважен. Например, программа выведет True, так как A и B — равные множества.

In [ ]:
A = {10, 2, 3}
B = {3, 2, 3, 10}
print(A == B)

Работа с элементами множеств

Из множества можно сделать список при помощи функции list или отсортированный список с помощью sorted. Узнать число элементов в множестве можно при помощи функции len.

In [ ]:
list(B), sorted(B), len(B), len(set())

s.copy() - возвращает копию множества;

s.clear() - удаляет все элементы множества

In [ ]:
B2 = B.copy()
B.clear()
B2, B

Проверить, принадлежит ли элемент множеству можно при помощи операции in, возвращающей значение типа bool. Аналогично есть противоположная операция not in.

In [ ]:
A = {1, 2, 3}
1 in A, 4 not in A

Для добавления элемента в множество есть метод add:

In [ ]:
A.add(4)
A

Для удаления элемента x из множества есть два метода: discard и remove. Их поведение различается только в случае, когда удаляемый элемент отсутствует в множестве. В этом случае метод discard не делает ничего, а метод remove генерирует исключение KeyError.

In [ ]:
A.discard(5)
A.remove(5)

Метод pop удаляет из множества один случайный элемент и возвращает его значение. Если же множество пусто, то генерируется исключение KeyError.

In [ ]:
A.pop(), A.pop(), A

Перебрать все элементы множества (в неопределенном порядке!) циклом for можно как и обычно:

In [ ]:
primes = {2, 3, 5, 7, 11}
for num in primes:
    print(num, end=' ')

Операции с множествами

С множествами в Питоне можно выполнять обычные для математики операции над множествами.

Операция Метод Что делает
A | B A.union(B) Возвращает множество, являющееся объединением множеств A и B.
A |= B A.update(B) Добавляет в множество A все элементы из множества B.
A & B A.intersection(B) Возвращает множество, являющееся пересечением множеств A и B.
A &= B A.intersection_update(B) Оставляет в множестве A только те элементы, которые есть в множестве B.
A - B A.difference(B) Возвращает разность множеств A и B (элементы, входящие в A, но не входящие в B).
A -= B A.difference_update(B) Удаляет из множества A все элементы, входящие в B.
A ^ B A.symmetric_difference(B) Возвращает симметрическую разность множеств A и B (элементы, входящие в A или в B, но не в оба из них одновременно).
A ^= B A.symmetric_difference_update(B) Записывает в A симметрическую разность множеств A и B.
A <= B A.issubset(B) Возвращает true, если A является подмножеством B.
A >= B A.issuperset(B) Возвращает true, если B является подмножеством A.
A < B Эквивалентно A <= B and A != B
A > B Эквивалентно A >= B and A != B
A.isdisjoint(B) Эквивалентно пустому A.intersection(B)

Frozen set

Тип frozenset - множество, которое является неизменяемым, а поэтому можнт использоваться как ключ словаря или как элемент множества. Его элементы нельзя изменить, поэтому методов add, remove и др. у него нет.

In [ ]:
frozenset([1,2,2])