К какой стандартной структуре данных в python нельзя обращаться по индексу
Перейти к содержимому

К какой стандартной структуре данных в python нельзя обращаться по индексу

  • автор:

Python: коллекции, часть 1/4: классификация, общие подходы и методы, конвертация

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

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

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

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

Будем рассматривать стандартные встроенные коллекционные типы данных в Python: список (list), кортеж (tuple), строку (string), множества (set, frozenset), словарь (dict). Коллекции из модуля collections рассматриваться не будут, хотя многое из статьи должно быть применимым и при работе с ними.

ОГЛАВЛЕНИЕ:
  1. Классификация коллекций;
  2. Общие подходы к работе с коллекциями;
  3. Общие методы для части коллекций;
  4. Конвертирование коллекций.

1. Классификация коллекций

image

image

Пояснения терминологии:

Индексированность – каждый элемент коллекции имеет свой порядковый номер — индекс. Это позволяет обращаться к элементу по его порядковому индексу, проводить слайсинг («нарезку») — брать часть коллекции выбирая исходя из их индекса. Детально эти вопросы будут рассмотрены в дальнейшем в отдельной статье.

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

Изменяемость коллекции — позволяет добавлять в коллекцию новых членов или удалять их после создания коллекции.

Примечание для словаря (dict):
  • сам словарь изменяем — можно добавлять/удалять новые пары ключ: значение;
  • значения элементов словаря — изменяемые и не уникальные;
  • а вот ключи — не изменяемые и уникальные, поэтому, например, мы не можем сделать ключом словаря список, но можем кортеж. Из уникальности ключей, так же следует уникальность элементов словаря — пар ключ: значение.

UPD: Важное замечание от sakutylev: Для того, чтобы объект мог быть ключом словаря, он должен быть хешируем. У кортежа, возможен случай, когда его элемент является не хешируемым объектом, и соответственно сам кортеж тогда тоже не является хешируемым и не может выступать ключом словаря.

a = (1, [2, 3], 4) print(type(a)) # b = # TypeError: unhashable type: 'list' 
a = <> print(type(a)) # b = print(type(b)) # c = print(type(c)) #

2 Общие подходы к работе с любой коллекцией

Разобравшись в классификацией, рассмотрим что можно делать с любой стандартной коллекцией независимо от её типа (в примерах список и словарь, но это работает и для всех остальных рассматриваемых стандартных типов коллекций):

# Зададим исходно список и словарь (скопировать перед примерами ниже): my_list = ['a', 'b', 'c', 'd', 'e', 'f'] my_dict =

2.1 Печать элементов коллекции с помощью функции print()

print(my_list) # ['a', 'b', 'c', 'd', 'e', 'f'] print(my_dict) # # Не забываем, что порядок элементов в неиндексированных коллекциях не сохраняется. 

2.2 Подсчёт количества членов коллекции с помощью функции len()

print(len(my_list)) # 6 print(len(my_dict)) # 6 - для словаря пара ключ-значение считаются одним элементом. print(len('ab c')) # 4 - для строки элементом является 1 символ 

2.3 Проверка принадлежности элемента данной коллекции c помощью оператора in

x in s — вернет True, если элемент входит в коллекцию s и False — если не входит
Есть и вариант проверки не принадлежности: x not in s, где есть по сути, просто добавляется отрицание перед булевым значением предыдущего выражения.

my_list = ['a', 'b', 'c', 'd', 'e', 'f'] print('a' in my_list) # True print('q' in my_list) # False print('a' not in my_list) # False print('q' not in my_list) # True 

Для словаря возможны варианты, понятные из кода ниже:

my_dict = print('a' in my_dict) # True - без указания метода поиск по ключам print('a' in my_dict.keys()) # True - аналогично примеру выше print('a' in my_dict.values()) # False - так как 'а' — ключ, не значение print(1 in my_dict.values()) # True 

Можно ли проверять пары? Можно!

print(('a',1) in my_dict.items()) # True print(('a',2) in my_dict.items()) # False 

Для строки можно искать не только один символ, но и подстроку:

print('ab' in 'abc') # True

2.4 Обход всех элементов коллекции в цикле for in

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

for elm in my_list: print(elm) 
Обратите внимание на следующие моменты:
  • Порядок обработки элементов для не индексированных коллекций будет не тот, как при их создании
  • У прохода в цикле по словарю есть свои особенности:

 for elm in my_dict: # При таком обходе словаря, перебираются только ключи # равносильно for elm in my_dict.keys() print(elm) for elm in my_dict.values(): # При желании можно пройти только по значениям print(elm) 

Но чаще всего нужны пары ключ(key) — значение (value).

for key, value in my_dict.items(): # Проход по .items() возвращает кортеж (ключ, значение), # который присваивается кортежу переменных key, value print(key, value) 

Чтобы этого избежать подобных побочных эффектов, можно, например, итерировать копию коллекции:

for elm in list(my_list): # Теперь можете удалять и добавлять элементы в исходный список my_list, # так как итерация идет по его копии. 

2.5 Функции min(), max(), sum()

  • Функции min(), max() — поиск минимального и максимального элемента соответственно — работают не только для числовых, но и для строковых значений.
  • sum() — суммирование всех элементов, если они все числовые.
print(min(my_list)) # a print(sum(my_dict.values())) # 21 

3 Общие методы для части коллекций

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

image

Объяснение работы методов и примеры:

    .count() — метод подсчета определенных элементов для неуникальных коллекций (строка, список, кортеж), возвращает сколько раз элемент встречается в коллекции.

my_list = [1, 2, 2, 2, 2, 3] print(my_list.count(2)) # 4 экземпляра элемента равного 2 print(my_list.count(5)) # 0 - то есть такого элемента в коллекции нет 
my_list = [1, 2, 2, 2, 2, 3] print(my_list.index(2)) # первый элемент равный 2 находится по индексу 1 (индексация с нуля!) print(my_list.index(5)) # ValueError: 5 is not in list - отсутствующий элемент выдаст ошибку! 
my_set = my_set_2 = my_set.copy() print(my_set_2 == my_set) # True - коллекции равны - содержат одинаковые значения print(my_set_2 is my_set) # False - коллекции не идентичны - это разные объекты с разными id 
my_set = print(my_set) # my_set.clear() print(my_set) # set() 

Особые методы сравнения множеств (set, frozenset)

  • set_a.isdisjoint(set_b) — истина, если set_a и set_b не имеют общих элементов.
  • set_b.issubset(set_a) — если все элементы множества set_b принадлежат множеству set_a, то множество set_b целиком входит в множество set_a и является его подмножеством (set_b — подмножество)
  • set_a.issuperset(set_b) — соответственно, если условие выше справедливо, то set_a — надмножество
set_a = set_b = # порядок элементов не важен! set_c = set_d = print(set_a.isdisjoint(set_c)) # True - нет общих элементов print(set_b.issubset(set_a)) # True - set_b целиком входит в set_a, значит set_b - подмножество print(set_a.issuperset(set_b)) # True - set_b целиком входит в set_a, значит set_a - надмножество 

При равенстве множеств они одновременно и подмножество и надмножество друг для друга

print(set_a.issuperset(set_d)) # True print(set_a.issubset(set_d)) # True 

4 Конвертация одного типа коллекции в другой

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

my_tuple = ('a', 'b', 'a') my_list = list(my_tuple) my_set = set(my_tuple) # теряем индексы и дубликаты элементов! my_frozenset = frozenset(my_tuple) # теряем индексы и дубликаты элементов! print(my_list, my_set, my_frozenset) # ['a', 'b', 'a'] frozenset() 

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

  • При преобразовании в множество теряются дублирующие элементы, так как множество содержит только уникальные элементы! Собственно, проверка на уникальность, обычно и является причиной использовать множество в задачах, где у нас есть в этом потребность.
  • При конвертации индексированной коллекции в неиндексированную теряется информация о порядке элементов, а в некоторых случаев она может быть критически важной!
  • После конвертации в не изменяемый тип, мы больше не сможем менять элементы коллекции — удалять, изменять, добавлять новые. Это может привести к ошибкам в наших функциях обработки данных, если они были написаны для работы с изменяемыми коллекциями.

Дополнительные детали:

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

Это ограничение можно обойти, создав словарь комбинируя ключи со значениями с использованием zip():

my_keys = ('a', 'b', 'c') my_values = [1, 2] # Если количество элементов разное - # будет отработано пока хватает на пары - лишние отброшены my_dict = dict(zip(my_keys, my_values)) print(my_dict) #
my_tuple = ('a', 'b', 'c') my_str = ''.join(my_tuple) print(my_str) # abc 
my_list = [1, [2, 3], 4] my_set = set(my_list) # TypeError: unhashable type: 'list'
  • TimeComplexity (aka «Big O» or «Big Oh») (на английском)
  • Complexity of Python Operations (на английском)
Приглашаю к обсуждению:
  • Если я где-то допустил неточность или не учёл что-то важное — пишите в комментариях, важные комментарии будут позже добавлены в статью с указанием вашего авторства.
  • Если какие-то моменты не понятны и требуется уточнение — пишите ваши вопросы в комментариях — или я или другие читатели дадут ответ, а дельные вопросы с ответами будут позже добавлены в статью.
  • python
  • программирование
  • начинающим
  • коллекции
  • структуры данных
  • Python
  • Программирование

Python: коллекции, часть 2/4: индексирование, срезы, сортировка

В данной статье мы продолжим изучать общие принципы работы со стандартными коллекциями (модуль collections в ней не рассматривается) Python.

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

ОГЛАВЛЕНИЕ:

1. Индексирование

1.1 Индексированные коллекции

Рассмотрим индексированные коллекции (их еще называют последовательности — sequences) — список (list), кортеж (tuple), строку (string).

Под индексированностью имеется ввиду, что элементы коллекции располагаются в определённом порядке, каждый элемент имеет свой индекс от 0 (то есть первый по счёту элемент имеет индекс не 1, а 0) до индекса на единицу меньшего длины коллекции (т.е. len(mycollection)-1).

1.2 Получение значения по индексу

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

При задании отрицательного индекса, последний элемент имеет индекс -1, предпоследний -2 и так далее до первого элемента индекс которого равен значению длины коллекции с отрицательным знаком, то есть (-len(mycollection).

элементы a b c d e
индексы 0 (-5) 1 (-4) 2 (-3) 3 (-2) 4 (-1)
 my_str = "abcde" print(my_str[0]) # a - первый элемент print(my_str[-1]) # e - последний элемент print(my_str[len(my_str)-1]) # e - так тоже можно взять последний элемент print(my_str[-2]) # d - предпоследний элемент 

Наши коллекции могут иметь несколько уровней вложенности, как список списков в примере ниже. Для перехода на уровень глубже ставится вторая пара квадратных скобок и так далее.

my_2lvl_list = [[1, 2, 3], ['a', 'b', 'c']] print(my_2lvl_list[0]) # [1, 2, 3] - первый элемент — первый вложенный список print(my_2lvl_list[0][0]) # 1 — первый элемент первого вложенного списка print(my_2lvl_list[1][-1]) # с — последний элемент второго вложенного списка 

1.3 Изменение элемента списка по индексу

Поскольку кортежи и строки у нас неизменяемые коллекции, то по индексу мы можем только брать элементы, но не менять их:

my_tuple = (1, 2, 3, 4, 5) print(my_tuple[0]) # 1 my_tuple[0] = 100 # TypeError: 'tuple' object does not support item assignment 

А вот для списка, если взятие элемента по индексу располагается в левой части выражения, а далее идёт оператор присваивания =, то мы задаём новое значение элементу с этим индексом.

my_list = [1, 2, 3, [4, 5]] my_list[0] = 10 my_list[-1][0] = 40 print(my_list) # [10, 2, 3, [40, 5]] 

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

my_list = [1, 2, 3, 4, 5] my_list[5] = 6 # IndexError: list assignment index out of range 

2 Срезы

2.1 Синтаксис среза

Очень часто, надо получить не один какой-то элемент, а некоторый их набор ограниченный определенными простыми правилами — например первые 5 или последние три, или каждый второй элемент — в таких задачах, вместо перебора в цикле намного удобнее использовать так называемый срез (slice, slicing).

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

Синтаксис среза похож на таковой для индексации, но в квадратных скобках вместо одного значения указывается 2-3 через двоеточие:

my_collection[start:stop:step] # старт, стоп и шаг
Особенности среза:
  • Отрицательные значения старта и стопа означают, что считать надо не с начала, а с конца коллекции.
  • Отрицательное значение шага — перебор ведём в обратном порядке справа налево.
  • Если не указан старт [:stop:step]— начинаем с самого края коллекции, то есть с первого элемента (включая его), если шаг положительный или с последнего (включая его), если шаг отрицательный (и соответственно перебор идет от конца к началу).
  • Если не указан стоп [start:: step] — идем до самого края коллекции, то есть до последнего элемента (включая его), если шаг положительный или до первого элемента (включая его), если шаг отрицательный (и соответственно перебор идет от конца к началу).
  • step = 1, то есть последовательный перебор слева направо указывать не обязательно — это значение шага по умолчанию. В таком случае достаточно указать [start:stop]
  • Можно сделать даже так [:] — это значит взять коллекцию целиком
  • ВАЖНО: При срезе, первый индекс входит в выборку, а второй нет!
    То есть от старта включительно, до стопа, где стоп не включается в результат. Математически это можно было бы записать как [start, stop) или пояснить вот таким правилом:
Примеры срезов в виде таблицы:

image

Код примеров из таблицы

col = 'abcdefg' print(col[:]) # abcdefg print(col[::-1]) # gfedcba print(col[::2]) # aceg print(col[1::2]) # bdf print(col[:1]) # a print(col[-1:]) # g print(col[3:4]) # d print(col[-3:]) # efg print(col[-3:1:-1]) # edc print(col[2:5]) # cde

2.2. Именованные срезы

Чтобы избавится от «магических констант», особенно в случае, когда один и тот же срез надо применять многократно, можно задать константы с именованными срезами с пользованием специальной функции slice()()

Примечание: Nonе соответствует опущенному значению по-умолчанию. То есть [:2] становится slice(None, 2), а [1::2] становится slice(1, None, 2).

person = ('Alex', 'Smith', "May", 10, 1980) NAME, BIRTHDAY = slice(None, 2), slice(2, None) # задаем константам именованные срезы # данные константы в квадратных скобках заменятся соответствующими срезами print(person[NAME]) # ('Alex', 'Smith') print(person[BIRTHDAY]) # ('May', 10, 1980) 
my_list = [1, 2, 3, 4, 5, 6, 7] EVEN = slice(1, None, 2) print(my_list[EVEN]) # [2, 4, 6] 

2.3 Изменение списка срезом

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

Проиллюстрируем это на примерах ниже:

    Даже если хотим добавить один элемент, необходимо передавать итерируемый объект, иначе будет ошибка TypeError: can only assign an iterable

my_list = [1, 2, 3, 4, 5] # my_list[1:2] = 20 # TypeError: can only assign an iterable my_list[1:2] = [20] # Вот теперь все работает print(my_list) # [1, 20, 3, 4, 5] 

Срез аналоги .append() и insert()

my_list = [1, 2, 3, 4, 5] my_list[5:] = [6] # вставляем в конец — лучше использовать .append(6) print(my_list) # [1, 2, 3, 4, 5, 6] my_list[0:0] = [0] # вставляем в начало — лучше использовать .insert(0, 0) print(my_list) # [0, 1, 2, 3, 4, 5, 6] my_list[3:3] = [25] # вставляем между элементами — лучше использовать .insert(3, 25) print(my_list) # [0, 1, 2, 25, 3, 4, 5, 6] 
my_list = [1, 2, 3, 4, 5] my_list[1:3] = [20, 30] print(my_list) # [1, 20, 30, 4, 5] my_list[1:3] = [0] # нет проблем заменить два элемента на один print(my_list) # [1, 0, 4, 5] my_list[2:] = [40, 50, 60] # или два элемента на три print(my_list) # [1, 0, 40, 50, 60] 
my_list = [1, 2, 3, 4, 5] my_list[:2] = [] # или del my_list[:2] print(my_list) # [3, 4, 5] 

2.4 Выход за границы индекса

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

Обращение к несуществующему индексу коллекции вызывает ошибку:

my_list = [1, 2, 3, 4, 5] print(my_list[-10]) # IndexError: list index out of range print(my_list[10]) # IndexError: list index out of range 

А в случае выхода границ среза за границы коллекции никакой ошибки не происходит:

my_list = [1, 2, 3, 4, 5] print(my_list[0:10]) # [1, 2, 3, 4, 5] — отработали в пределах коллекции print(my_list[10:100]) # [] - таких элементов нет — вернули пустую коллекцию print(my_list[10:11]) # [] - проверяем 1 отсутствующий элемент - пустая коллекция, без ошибки 

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

3 Сортировка элементов коллекции

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

3.1 Функция sorted()

Мы может использовать функцию sorted() для вывода списка сортированных элементов любой коллекции для последующее обработки или вывода.

  • функция не меняет исходную коллекцию, а возвращает новый список из ее элементов;
  • не зависимо от типа исходной коллекции, вернётся список (list) ее элементов;
  • поскольку она не меняет исходную коллекцию, ее можно применять к неизменяемым коллекциям;
  • Поскольку при сортировке возвращаемых элементов нам не важно, был ли у элемента некий индекс в исходной коллекции, можно применять к неиндексированным коллекциям;
  • Имеет дополнительные не обязательные аргументы:
    reverse=True — сортировка в обратном порядке
    key=funcname (начиная с Python 2.4) — сортировка с помощью специальной функции funcname, она может быть как стандартной функцией Python, так и специально написанной вами для данной задачи функцией и лямбдой.
my_list = [2, 5, 1, 7, 3] my_list_sorted = sorted(my_list) print(my_list_sorted) # [1, 2, 3, 5, 7] my_set = my_set_sorted = sorted(my_set, reverse=True) print(my_set_sorted) # [7, 5, 3, 2, 1] 

Пример сортировки списка строк по длине len() каждого элемента:

my_files = ['somecat.jpg', 'pc.png', 'apple.bmp', 'mydog.gif'] my_files_sorted = sorted(my_files, key=len) print(my_files_sorted) # ['pc.png', 'apple.bmp', 'mydog.gif', 'somecat.jpg'] 

3.2 Функция reversed()

Функция reversed() применяется для последовательностей и работает по другому:

  • возвращает генератор списка, а не сам список;
  • если нужно получить не генератор, а готовый список, результат можно обернуть в list() или же вместо reversed() воспользоваться срезом [: :-1];
  • она не сортирует элементы, а возвращает их в обратном порядке, то есть читает с конца списка;
  • из предыдущего пункта понятно, что если у нас коллекция неиндексированная — мы не можем вывести её элементы в обратном порядке и эта функция к таким коллекциям не применима — получим «TypeError: argument to reversed() must be a sequence»;
  • не позволяет использовать дополнительные аргументы — будет ошибка «TypeError: reversed() does not take keyword arguments».
my_list = [2, 5, 1, 7, 3] my_list_sorted = reversed(my_list) print(my_list_sorted) # print(list(my_list_sorted)) # [3, 7, 1, 5, 2] print(my_list[::-1]) # [3, 7, 1, 5, 2] - тот же результат с помощью среза

3.3 Методы списка .sort() и .reverse()

У списка (и только у него) есть особые методы .sort() и .reverse() которые делают тоже самое, что соответствующие функции sorted() и reversed(), но при этом:

  • Меняют сам исходный список, а не генерируют новый;
  • Возвращают None, а не новый список;
  • поддерживают те же дополнительные аргументы;
  • в них не надо передавать сам список первым параметром, более того, если это сделать — будет ошибка — не верное количество аргументов.
my_list = [2, 5, 1, 7, 3] my_list.sort() print(my_list) # [1, 2, 3, 5, 7] 

Обратите внимание: Частая ошибка начинающих, которая не является ошибкой для интерпретатора, но приводит не к тому результату, который хотят получить.

my_list = [2, 5, 1, 7, 3] my_list = my_list.sort() print(my_list) # None 

3.4 Особенности сортировки словаря

В сортировке словаря есть свои особенности, вызванные тем, что элемент словаря — это пара ключ: значение.

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

  • sorted(my_dict) — когда мы передаем в функцию сортировки словарь без вызова его дополнительных методов — идёт перебор только ключей, сортированный список ключей нам и возвращается;
  • sorted(my_dict.keys()) — тот же результат, что в предыдущем примере, но прописанный более явно;
  • sorted(my_dict.items()) — возвращается сортированный список кортежей (ключ, значение), сортированных по ключу;
  • sorted(my_dict.values()) — возвращается сортированный список значений
my_dict = mysorted = sorted(my_dict) print(mysorted) # ['a', 'b', 'c', 'd', 'e', 'f'] mysorted = sorted(my_dict.items()) print(mysorted) # [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6)] mysorted = sorted(my_dict.values()) print(mysorted) # [1, 2, 3, 4, 5, 6] 

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

Для решения этой задачи можно в качестве специальной функции сортировки передавать lambda-функцию lambda x: x[1] которая из получаемых на каждом этапе кортежей (ключ, значение) будет брать для сортировки второй элемент кортежа.

population = # отсортируем по возрастанию населения: population_sorted = sorted(population.items(), key=lambda x: x[1]) print(population_sorted) # [('Delhi', 16787941), ('Beijing', 21516000), ('Karachi', 23500000), ('Shanghai', 24256800)] 

UPD от ShashkovS: 3.5 Дополнительная информация по использованию параметра key при сортировке

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

shop = [('каретка', 1200), ('шатун', 1000), ('седло', 300), ('педаль', 100), ('седло', 1500), ('рама', 12000), ('обод', 2000), ('шатун', 200), ('седло', 2700)] def prepare_item(item): return (item[0], -item[1]) shop.sort(key=prepare_item) 

Результат сортировки

for det, price in shop: print(' цена: 5>р.'.format(det, price)) # каретка цена: 1200р. # обод цена: 2000р. # педаль цена: 100р. # рама цена: 12000р. # седло цена: 2700р. # седло цена: 1500р. # седло цена: 300р. # шатун цена: 1000р. # шатун цена: 200р. 

Перед тем, как сравнивать два элемента списка к ним применялась функция prepare_item, которая меняла знак у стоимости (функция применяется ровно по одному разу к каждому элементу. В результате при одинаковом первом значении сортировка по второму происходила в обратном порядке.

Чтобы не плодить утилитарные функции, вместо использования сторонней функции, того же эффекта можно добиться с использованием лямбда-функции.

# Данные скопировать из примера выше shop.sort(key=lambda x: (x[0], -x[1]))
  • wiki.python.org/moin/HowTo/Sorting#Key_Functions (на английском).
  • habrahabr.ru/post/319200/#comment_10011882 — еще один комментарий с детальным примером ShashkovS к данной статье

UPD от ShashkovS: 3.6 Устойчивость сортировки

Допустим данные нужно отсортировать сначала по столбцу А по возрастанию, затем по столбцу B по убыванию, и наконец по столбцу C снова по возрастанию.

Если данные в столбце B числовые, то при помощи подходящей функции в key можно поменять знак у элементов B, что приведёт к необходимому результату.
А если все данные текстовые? Тут есть такая возможность.
Дело в том, что сортировка sort в Python устойчивая (начиная с Python 2.2), то есть она не меняет порядок «одинаковых» элементов.

Поэтому можно просто отсортировать три раза по разным ключам:

data.sort(key=lambda x: x['C']) data.sort(key=lambda x: x['B'], reverse=True) data.sort(key=lambda x: x['А']) 

Дополнительная информация по устойчивости сортировки и примеры: wiki.python.org/moin/HowTo/Sorting#Sort_Stability_and_Complex_Sorts (на наглийском).

Часть 1 Часть 2 Часть 3 Часть 4
Приглашаю к обсуждению:
  • Если я где-то допустил неточность или не учёл что-то важное — пишите в комментариях, важные комментарии будут позже добавлены в статью с указанием вашего авторства.
  • Если какие-то моменты не понятны и требуется уточнение — пишите ваши вопросы в комментариях — или я или другие читатели дадут ответ, а дельные вопросы с ответами будут позже добавлены в статью.

Списки, кортежи и словари

Для работы с наборами данных Python предоставляет такие встроенные типы как списки, кортежи и словари.

Список (list) представляет тип данных, который хранит набор или последовательность элементов. Во многих языках программирования есть аналогичная структура данных, которая называется массив.

Создание списка

Для создания списка применяются квадратные скобки [] , внутри которых через запятую перечисляются элементы списка. Например, определим список чисел:

numbers = [1, 2, 3, 4, 5]

Подобным образом можно определять списки с данными других типов, например, определим список строк:

people = ["Tom", "Sam", "Bob"]

Также для создания списка можно использовать функцию-конструктор list() :

numbers1 = [] numbers2 = list()

Оба этих определения списка аналогичны — они создают пустой список.

Список необязательно должен содержать только однотипные объекты. Мы можем поместить в один и тот же список одновременно строки, числа, объекты других типов данных:

objects = [1, 2.6, "Hello", True]

Для проверки элементов списка можно использовать стандартную функцию print, которая выводит содержимое списка в удобочитаемом виде:

numbers = [1, 2, 3, 4, 5] people = ["Tom", "Sam", "Bob"] print(numbers) # [1, 2, 3, 4, 5] print(people) # ["Tom", "Sam", "Bob"]

Конструктор list может принимать набор значений, на основе которых создается список:

numbers1 = [1, 2, 3, 4, 5] numbers2 = list(numbers1) print(numbers2) # [1, 2, 3, 4, 5] letters = list("Hello") print(letters) # ['H', 'e', 'l', 'l', 'o']

Если необходимо создать список, в котором повторяется одно и то же значение несколько раз, то можно использовать символ звездочки *, то есть фактически применить операцию умножения к уже существующему списку:

numbers = [5] * 6 # 6 раз повторяем 5 print(numbers) # [5, 5, 5, 5, 5, 5] people = ["Tom"] * 3 # 3 раза повторяем "Tom" print(people) # ["Tom", "Tom", "Tom"] students = ["Bob", "Sam"] * 2 # 2 раза повторяем "Bob", "Sam" print(students) # ["Bob", "Sam", "Bob", "Sam"]

Обращение к элементам списка

Для обращения к элементам списка надо использовать индексы, которые представляют номер элемента в списка. Индексы начинаются с нуля. То есть первый элемент будет иметь индекс 0, второй элемент — индекс 1 и так далее. Для обращения к элементам с конца можно использовать отрицательные индексы, начиная с -1. То есть у последнего элемента будет индекс -1, у предпоследнего — -2 и так далее.

people = ["Tom", "Sam", "Bob"] # получение элементов с начала списка print(people[0]) # Tom print(people[1]) # Sam print(people[2]) # Bob # получение элементов с конца списка print(people[-2]) # Sam print(people[-1]) # Bob print(people[-3]) # Tom

Для изменения элемента списка достаточно присвоить ему новое значение:

people = ["Tom", "Sam", "Bob"] people[1] = "Mike" # изменение второго элемента print(people[1]) # Mike print(people) # ["Tom", "Mike", "Bob"]

Разложение списка

Python позволяет разложить список на отдельные элементы:

people = ["Tom", "Bob", "Sam"] tom, bob, sam = people print(tom) # Tom print(bob) # Bob print(sam) # Sam

В данном случае переменным tom, bob и sam последовательно присваиваются элементы из списка people. Однако следует учитывать, что количество переменных должно быть равно числу элементов присваиваемого списка.

Перебор элементов

Для перебора элементов можно использовать как цикл for, так и цикл while.

Перебор с помощью цикла for :

people = ["Tom", "Sam", "Bob"] for person in people: print(person)

Здесь будет производиться перебор списка people, и каждый его элемент будет помещаться в переменную person.

Перебор также можно сделать с помощью цикла while :

people = ["Tom", "Sam", "Bob"] i = 0 while i < len(people): print(people[i]) # применяем индекс для получения элемента i += 1

Для перебора с помощью функции len() получаем длину списка. С помощью счетчика i выводит по элементу, пока значение счетчика не станет равно длине списка.

Сравнение списков

Два списка считаются равными, если они содержат один и тот же набор элементов:

numbers1 = [1, 2, 3, 4, 5] numbers2 = list([1, 2, 3, 4, 5]) if numbers1 == numbers2: print("numbers1 equal to numbers2") else: print("numbers1 is not equal to numbers2")

В данном случае оба списка будут равны.

Получение части списка

Если необходимо получить какую-то определенную часть списка, то мы можем применять специальный синтаксис, который может принимать следующие формы:

  • list[:end] : через параметр end передается индекс элемента, до которого нужно копировать список
  • list[start:end] : параметр start указывает на индекс элемента, начиная с которого надо скопировать элементы
  • list[start:end:step] : параметр step указывает на шаг, через который будут копироваться элементы из списка. По умолчанию этот параметр равен 1.
people = ["Tom", "Bob", "Alice", "Sam", "Tim", "Bill"] slice_people1 = people[:3] # с 0 по 3 print(slice_people1) # ["Tom", "Bob", "Alice"] slice_people2 = people[1:3] # с 1 по 3 print(slice_people2) # ["Bob", "Alice"] slice_people3 = people[1:6:2] # с 1 по 6 с шагом 2 print(slice_people3) # ["Bob", "Sam", "Bill"]

Можно использовать отрицательные индексы, тогда отсчет будет идти с конца, например, -1 - предпоследний, -2 - третий сконца и так далее.

people = ["Tom", "Bob", "Alice", "Sam", "Tim", "Bill"] slice_people1 = people[:-1] # с предпоследнего по нулевой print(slice_people1) # ["Tom", "Bob", "Alice", "Sam", "Tim", "Bill"] slice_people2 = people[-3:-1] # с третьего с конца по предпоследний print(slice_people2) # [ "Sam", "Tim"]

Методы и функции по работе со списками

Для управления элементами списки имеют целый ряд методов. Некоторые из них:

  • append(item) : добавляет элемент item в конец списка
  • insert(index, item) : добавляет элемент item в список по индексу index
  • extend(items) : добавляет набор элементов items в конец списка
  • remove(item) : удаляет элемент item. Удаляется только первое вхождение элемента. Если элемент не найден, генерирует исключение ValueError
  • clear() : удаление всех элементов из списка
  • index(item) : возвращает индекс элемента item. Если элемент не найден, генерирует исключение ValueError
  • pop([index]) : удаляет и возвращает элемент по индексу index. Если индекс не передан, то просто удаляет последний элемент.
  • count(item) : возвращает количество вхождений элемента item в список
  • sort([key]) : сортирует элементы. По умолчанию сортирует по возрастанию. Но с помощью параметра key мы можем передать функцию сортировки.
  • reverse() : расставляет все элементы в списке в обратном порядке
  • copy() : копирует список

Кроме того, Python предоставляет ряд встроенных функций для работы со списками:

  • len(list) : возвращает длину списка
  • sorted(list, [key]) : возвращает отсортированный список
  • min(list) : возвращает наименьший элемент списка
  • max(list) : возвращает наибольший элемент списка

Добавление и удаление элементов

Для добавления элемента применяются методы append() , extend и insert , а для удаления - методы remove() , pop() и clear() .

people = ["Tom", "Bob"] # добавляем в конец списка people.append("Alice") # ["Tom", "Bob", "Alice"] # добавляем на вторую позицию people.insert(1, "Bill") # ["Tom", "Bill", "Bob", "Alice"] # добавляем набор элементов ["Mike", "Sam"] people.extend(["Mike", "Sam"]) # ["Tom", "Bill", "Bob", "Alice", "Mike", "Sam"] # получаем индекс элемента index_of_tom = people.index("Tom") # удаляем по этому индексу removed_item = people.pop(index_of_tom) # ["Bill", "Bob", "Alice", "Mike", "Sam"] # удаляем последний элемент last_item = people.pop() # ["Bill", "Bob", "Alice", "Mike"] # удаляем элемент "Alice" people.remove("Alice") # ["Bill", "Bob", "Mike"] print(people) # ["Bill", "Bob", "Mike"] # удаляем все элементы people.clear() print(people) # []

Проверка наличия элемента

Если определенный элемент не найден, то методы remove и index генерируют исключение. Чтобы избежать подобной ситуации, перед операцией с элементом можно проверять его наличие с помощью ключевого слова in :

people = ["Tom", "Bob", "Alice", "Sam"] if "Alice" in people: people.remove("Alice") print(people) # ["Tom", "Bob", "Sam"]

Выражение if "Alice" in people возвращает True, если элемент "Alice" имеется в списке people. Поэтому конструкция if "Alice" in people может выполнить последующий блок инструкций в зависимости от наличия элемента в списке.

Удаление с помощью del

Python также поддерживает еще один способ удаления элементов списка - с помощью оператора del . В качестве параметра этому оператору передается удаляемый элемент или набор элементов:

people = ["Tom", "Bob", "Alice", "Sam", "Bill", "Kate", "Mike"] del people[1] # удаляем второй элемент print(people) # ["Tom", "Alice", "Sam", "Bill", "Kate", "Mike"] del people[:3] # удаляем по четвертый элемент не включая print(people) # ["Bill", "Kate", "Mike"] del people[1:] # удаляем со второго элемента print(people) # ["Bill"]

Изменение подсписка

Для изменения подсписка - набора элементов в списке можно использовать вышерассмотренный синтаксис [start:end] :

nums = [10, 20, 30, 40, 50] nums[1:4]=[11, 22] print(nums) # [10, 11, 22, 50]

Здесь выражение nums[1:4] фактически обращается к подсписку [20, 30, 40] . Присвоение этому подсписку списка [11, 22] позволяет заменить элемента с 1 по 4 индекс не включая на элементы [11, 22] . И после изменения получим список [10, 11, 22, 50]

Подсчет вхождений

Если необходимо узнать, сколько раз в списке присутствует тот или иной элемент, то можно применить метод count() :

people = ["Tom", "Bob", "Alice", "Tom", "Bill", "Tom"] people_count = people.count("Tom") print(people_count) # 3

Сортировка

Для сортировки по возрастанию применяется метод sort() :

people = ["Tom", "Bob", "Alice", "Sam", "Bill"] people.sort() print(people) # ["Alice", "Bill", "Bob", "Sam", "Tom"]

Если необходимо отсортировать данные в обратном порядке, то мы можем после сортировки применить метод reverse() :

people = ["Tom", "Bob", "Alice", "Sam", "Bill"] people.sort() people.reverse() print(people) # ["Tom", "Sam", "Bob", "Bill", "Alice"]

При сортировке фактически сравниваются два объекта, и который из них "меньше", ставится перед тем, который "больше". Понятия "больше" и "меньше" довольно условны. И если для чисел все просто - числа расставляются в порядке возрастания, то для строк и других объектов ситуация сложнее. В частности, строки оцениваются по первым символам. Если первые символы равны, оцениваются вторые символы и так далее. При чем цифровой символ считается "меньше", чем алфавитный заглавный символ, а заглавный символ считается меньше, чем строчный.

Таким образом, если в списке сочетаются строки с верхним и нижним регистром, то мы можем получить не совсем корректные результаты, так как для нас строка "bob" должна стоять до строки "Tom". И чтобы изменить стандартное поведение сортировки, мы можем передать в метод sort() в качестве параметра функцию:

people = ["Tom", "bob", "alice", "Sam", "Bill"] people.sort() # стандартная сортировка print(people) # ["Bill", "Sam", "Tom", "alice", "bob"] people.sort(key=str.lower) # сортировка без учета регистра print(people) # ["alice", "Bill", "bob", "Sam", "Tom"]

Кроме метода sort мы можем использовать встроенную функцию sorted , которая имеет две формы:

  • sorted(list) : сортирует список list
  • sorted(list, key) : сортирует список list, применяя к элементам функцию key
people = ["Tom", "bob", "alice", "Sam", "Bill"] sorted_people = sorted(people, key=str.lower) print(sorted_people) # ["alice", "Bill", "bob", "Sam", "Tom"]

При использовании этой функции следует учитывать, что эта функция не изменяет сортируемый список, а все отсортированные элементы она помещает в новый список, который возвращается в качестве результата.

Минимальное и максимальное значения

Встроенный функции Python min() и max() позволяют найти минимальное и максимальное значения соответственно:

numbers = [9, 21, 12, 1, 3, 15, 18] print(min(numbers)) # 1 print(max(numbers)) # 21

Копирование списков

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

people1 = ["Tom", "Bob", "Alice"] people2 = people1 people2.append("Sam") # добавляем элемент во второй список # people1 и people2 указывают на один и тот же список print(people1) # ["Tom", "Bob", "Alice", "Sam"] print(people2) # ["Tom", "Bob", "Alice", "Sam"]

Это так называемое "поверхностное копирование" (shallow copy). И, как правило, такое поведение нежелательное. И чтобы происходило копирование элементов, но при этом переменные указывали на разные списки, необходимо выполнить глубокое копирование (deep copy). Для этого можно использовать метод copy() :

people1 = ["Tom", "Bob", "Alice"] people2 = people1.copy() # копируем элементы из people1 в people2 people2.append("Sam") # добавляем элемент ТОЛЬКО во второй список # people1 и people2 указывают на разные списки print(people1) # ["Tom", "Bob", "Alice"] print(people2) # ["Tom", "Bob", "Alice", "Sam"]

Соединение списков

Для объединения списков применяется операция сложения (+):

people1 = ["Tom", "Bob", "Alice"] people2 = ["Tom", "Sam", "Tim", "Bill"] people3 = people1 + people2 print(people3) # ["Tom", "Bob", "Alice", "Tom", "Sam", "Tim", "Bill"]

Списки списков

Списки кроме стандартных данных типа строк, чисел, также могут содержать другие списки. Подобные списки можно ассоциировать с таблицами, где вложенные списки выполняют роль строк. Например:

people = [ ["Tom", 29], ["Alice", 33], ["Bob", 27] ] print(people[0]) # ["Tom", 29] print(people[0][0]) # Tom print(people[0][1]) # 29

Чтобы обратиться к элементу вложенного списка, необходимо использовать пару индексов: people[0][1] - обращение ко второму элементу первого вложенного списка.

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

people = [ ["Tom", 29], ["Alice", 33], ["Bob", 27] ] # создание вложенного списка person = list() person.append("Bill") person.append(41) # добавление вложенного списка people.append(person) print(people[-1]) # ["Bill", 41] # добавление во вложенный список people[-1].append("+79876543210") print(people[-1]) # ["Bill", 41, "+79876543210"] # удаление последнего элемента из вложенного списка people[-1].pop() print(people[-1]) # ["Bill", 41] # удаление всего последнего вложенного списка people.pop(-1) # изменение первого элемента people[0] = ["Sam", 18] print(people) # [ ["Sam", 18], ["Alice", 33], ["Bob", 27]]

Перебор вложенных списков:

people = [ ["Tom", 29], ["Alice", 33], ["Bob", 27] ] for person in people: for item in person: print(item, end=" | ")
Tom | 29 | Alice | 33 | Bob | 27 |

Списки, словари и множества в Python

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

Индексирование и присваивание

Две частые операции - индексирование и присваивание на позицию индекса. В списках Python значения присваиваются и извлекаются из определенных известных мест памяти. Независимо от того, насколько велик список, индексный поиск и присвоение занимают постоянное количество времени и, таким образом их трудоемкость O(1).

Pop, Shift, Delete

Извлечение элемента(pop) из списка Python по умолчанию выполняется с конца, но, передавая индекс, вы можете получить элемент из определенной позиции. Когда pop вызывается с конца, операция имеет сложность O(1) , а вызов pop из любого места - O(n). Откуда такая разница?

Когда элемент берется из середины списка Python, все остальные элементы в списке сдвигаются на одну позицию ближе к началу. Это суровая плата за возможность брать индекс за O(1), что является более частой операцией.

По тем же причинам вставка в индекс - O(N); каждый последующий элемент должен быть сдвинут на одну позицию ближе к концу, чтобы разместить новый элемент. Неудивительно, что удаление ведет себя таким же образом.

Итерирование

Итерирование выполняется за O(N), потому что для итерации по N элементам требуется N шагов. Это также объясняет, почему оператор in, max, min в Python является O(N): чтобы определить, находится ли элемент в списке, мы должны перебирать каждый элемент.

Срезы

Чтобы получить доступ к фрагменту [a: b] списка, мы должны перебрать каждый элемент между индексами a и b. Таким образом, доступ к срезу - O(k), где k - размер среза. Удаление среза O(N) по той же причине, что удаление одного элемента - O(N): N последующих элементов должны быть смещены в сторону начала списка.

Умножение на int

Чтобы понять умножение списка на целое k, вспомним, что конкатенация выполняется за O(M), где M - длина добавленного списка. Из этого следует, что умножение списка равно O(N k), так как умножение k-размера списка N раз потребует времени k (N-1).

Разворот списка

Разворот списка - это O(N), так как мы должны переместить каждый элемент.

2. Множества

Множество (set)

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

Действия, которые можно выполнять с множеством:

  1. добавлять и удалять элементы,
  2. проверять принадлежность элемента множеству,
  3. перебирать его элементы,
  4. выполнять операции над множествами (объединение, пересечение, разность).

Операция “проверить принадлежность элемента” выполняется в множестве намного быстрее, чем в списке.

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

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

Задание множеств

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

Исключением явлеется пустое множество:

A = set() # A -- множество D = <> # D -- не пустое множество, а пустой словарь!

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

>>> A = set('qwerty') >>> print(A) .

Каждый элемент может входить в множество только один раз.

>>> A = >>> B = >>> print(A == B) # A и B — равные множества. True >>> set('Hello')

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

Операция Значение Трудоемкость
x in A принадлежит ли элемент x множеству A (возвращают значение типа bool ) O(1)
x not in A то же, что not x in A O(1)
A.add(x) добавить элемент x в множество A O(1)
A.discard(x) удалить элемент x из множества A O(1)
A.remove(x) удалить элемент x из множества A O(1)
A.pop() удаляет из множества один случайный элемент и возвращает его O(1)

Как мы видим, по времени стандартные оперцаии с одним элементом множества выполняются за O(1).

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

При помощи цикла for можно перебрать все элементы множества:

Primes = for num im Primes: print(num)

Из множества можно сделать список при помощи функции list :

>>> A = >>> B = list(A) [1, 2, 3, 4, 5]
Упражнение №2

Вывести на экран все элементы множества A, которых нет в множестве B.

A = set('bqlpzlkwehrlulsdhfliuywemrlkjhsdlfjhlzxcovt') B = set('zmxcvnboaiyerjhbziuxdytvasenbriutsdvinjhgik') for x in A: .

Операции с множествами, обычные для математики

Операция Значение Трудоемкость
A | B
A.union(B)
Возвращает множество, являющееся объединением множеств A и B . O(len(A)+len(B))
A | = B
A.update(B)
Записывает в A объединение множеств A и B . O(len(A)+len(B))
A & B
A.intersection(B)
Возвращает множество, являющееся пересечением множеств A и B . O(min(len(A), len(B))
A &= B
A.intersection_update(B)
Записывает в A пересечение множеств A и B . O(min(len(A), len(B))
A - B
A.difference(B)
Возвращает разность множеств A и B (элементы, входящие в A, но не входящие в B). O(len(A)+len(B))
A -= B
A.difference_update(B)
Записывает в A разность множеств A и B . O(len(A)+len(B))
A ^ B
A.symmetric_difference(B)
Возвращает симметрическую разность множеств A и B (элементы, входящие в A или в B, но не в оба из них одновременно). O(len(A)+len(B))
A ^= B
A.symmetric_difference_update(B)
Записывает в A симметрическую разность множеств A и B . O(len(A)+len(B))
A A.issubset(B) Возвращает True, если A является подмножеством B. O(len(A))
A >=
B A.issuperset(B)
Возвращает True, если B является подмножеством A. O(len(B))
A < B Эквивалентно A

O(len(A))
A > B Эквивалентно A >= B and A != B O(len(B))

В случае, если нужно провести процедуру, затрагивающую все элементы множества, то его трудоемкость будет O(N).

3. Словари

Словарь (ассоциативный массив, dict)

В массиве или в списке индекс - это целое число. Традиционной является следующая ситуация:

>>> Days = ['Sunday', 'Monday', 'Tuesday', 'Wednessday', 'Thursday', 'Friday', 'Saturday'] >>> Days[0] 'Sunday' >>> Days[1] 'Monday'

А как реализовать обратное соответствие?

>>> Days['Sunday'] 0 >>> Days['Monday'] 1

При помощи списка или массива это сделать невозможно, нужно использовать ассоциативный массив или словарь.

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

Days = < 'Sunday': 0, 'Monday': 1, 'Tuesday': 2, 'Wednessday': 3, 'Thursday': 4, 'Friday': 5, 'Saturday': 6 >>>> Days['Sunday'] 0 >>> Days['Monday'] 1 >>> Days['Yesterday'] Traceback (most recent call last): File "", line 1, in KeyError: 'Yesterday'

При попытке обратиться к несуществующему элементу ассоциативного массива мы получаем исключение KeyError .

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

>>> Days['Yesterday'] = -1 >>> print(Days['Yesterday']) -1

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

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

>>> Days['Tomorrow'] = -1 >>> Days['Yesterday'] == Days['Tomorrow'] True

Ключом может быть произвольный неизменяемый тип данных: целые и действительные числа, строки, кортежи. Ключом в словаре не может быть множество, но может быть элемент типа frozenset: специальный тип данных, являющийся аналогом типа set, который нельзя изменять после создания. Значением элемента словаря может быть любой тип данных, в том числе и изменяемый.

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

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

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

Capitals = Capitals = dict(Russia = 'Moscow', Ukraine = 'Kiev', USA = 'Washington') Capitals = dict([("Russia", "Moscow"), ("Ukraine", "Kiev"), ("USA", "Washington")]) Capitals = dict(zip(["Russia", "Ukraine", "USA"], ["Moscow", "Kiev", "Washington"]))

Также можно использовать генерацию словаря через Dict comprehensions:

Cities = ["Moscow", "Kiev", "Washington"] States = ["Russia", "Ukraine", "USA"] CapitalsOfState =

Это особенно полезно, когда нужно "вывернуть" словарь наизнанку:

StateByCapital =

Операции с элементами словарей

Операция Значение Трудоемкость
value = A[key] Получение элемента по ключу. Если элемента с заданным ключом в словаре нет, то возникает исключение KeyError. O(1)
value = A.get(key) Получение элемента по ключу. Если элемента в словаре нет, то get возвращает None. O(1)
value = A.get(key, default_value) То же, но вместо None метод get возвращает default_value. O(1)
key in A Проверить принадлежность ключа словарю. O(1)
key not in A То же, что not key in A. O(1)
A[key] = value Добавление нового элемента в словарь. O(1)
del A[key] Удаление пары ключ-значение с ключом key. Возбуждает исключение KeyError, если такого ключа нет. O(1)
if key in A: del A[key] Удаление пары ключ-значение с предварительной проверкой наличия ключа. O(1)
try: del A[key] except KeyError: pass Удаление пары ключ-значение с перехватыванием и обработкой исключения. O(1)
value = A.pop(key) Удаление пары ключ-значение с ключом key и возврат значения удаляемого элемента.Если такого ключа нет, то возбуждается KeyError. O(1)
value = A.pop(key, default_value) То же, но вместо генерации исключения возвращается default_value. O(1)
A.pop(key, None) Это позволяет проще всего организовать безопасное удаление элемента из словаря. O(1)
len(A) Возвращает количество пар ключ-значение, хранящихся в словаре. O(1)

Перебор элементов словаря по ключу

for key in A: print(key, A[key])

Представления элементов словаря

Представления во многом похожи на списки, но они остаются связанными со своим исходным словарём и изменяются, если менять значения элементов словаря.

  • Метод keys возвращает представление ключей всех элементов.
  • Метод values возвращает представление всех значений.
  • Метод items возвращает представление всех пар (кортежей) из ключей и значений.
>>> A = dict(a='a', b='b', c='c') >>> k = A.keys() >>> v = A.values() >>> k, v (dict_keys(['c', 'b', 'a']), dict_values(['c', 'b', 'a'])) >>> A['d'] = 'a' >>> k, v (dict_keys(['d', 'c', 'b', 'a']), dict_values(['a', 'c', 'b', 'a']))

Учтите что итерироваться по представлениям изменяя словарь нельзя

>>> for key in A.keys(): . del A[key] . Traceback (most recent call last): File "", line 1, in RuntimeError: dictionary changed size during iteration

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

>>> for key in list(A.keys()): . del A[key] . >>> A <>

Пример использования словаря

# Создадим пустой словать Capitals Capitals = dict() # Заполним его несколькими значениями Capitals['Russia'] = 'Moscow' Capitals['Ukraine'] = 'Kiev' Capitals['USA'] = 'Washington' # Считаем название страны print('В какой стране вы живете?') country = input() # Проверим, есть ли такая страна в словаре Capitals if country in Capitals: # Если есть - выведем ее столицу print('Столица вашей страны', Capitals[country]) else: # Запросим название столицы и добавим его в словарь print('Как называется столица вашей страны?') city = input() Capitals[country] = city

Трудоемкость стандартных операций

Второй основной тип данных Python - это словарь. Как вы помните, словарь отличается от списка возможностью доступа к элементам по ключу, а не позиции. На данный момент наиболее важной характеристикой является то, что получение и присваивание элемента в словаре являются операциями за O(1).

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

Другая важная операция словаря - проверка наличия ключа в словаре. Операция contains также работает за O(1) (в случае со списками это занимало O(N)), потому что проверка для данного ключа подразумевает простое получение элемента по ключу, которое делается за O(1).

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

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

  • Подсчет числа каких-то объектов. В этом случае нужно завести словарь, в котором ключами являются объекты, а значениями — их количество.
  • Хранение каких-либо данных, связанных с объектом. Ключи — объекты, значения — связанные с ними данные. Например, если нужно по названию месяца определить его порядковый номер, то это можно сделать при помощи словаря Num['January'] = 1; Num['February'] = 2; .
  • Установка соответствия между объектами (например, “родитель—потомок”). Ключ — объект, значение — соответствующий ему объект.
  • Если нужен обычный массив, но при этом масимальное значение индекса элемента очень велико, но при этом будут использоваться не все возможные индексы (так называемый “разреженный массив”), то можно использовать ассоциативный массив для экономии памяти.

4. Задача №3768. Контрольная по ударениям

Вариант 1. Используем множество

Вариант 2. Используем словарь

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *