# Вспомним, как работает параметр key при сортировке
names = ["Оля", "Юля", "Петя", "Маша", "Аня", "Света"]
# Сортируем просто лексикографически
list(sorted(names))
['Аня', 'Маша', 'Оля', 'Петя', 'Света', 'Юля']
# Пусть теперь надо отсортировать строки по их длине
# Передадим в качестве параметра стандартную функцию len
list(sorted(names, key=len))
['Оля', 'Юля', 'Аня', 'Петя', 'Маша', 'Света']
# Встоенная сортировка является стабильной:
# порядок строк с одинаковой длиной не изменился
# Пусть теперь нужно отсортировать строки сначала по длине,
# а при равных длинах - лексикографически
# Студенты на лекции предложили такой способ:
# сначала сортируем лексикографически, а потом - стабильно по длине
list(sorted(sorted(names), key=len))
['Аня', 'Оля', 'Юля', 'Маша', 'Петя', 'Света']
# Можно обойтись и одной сортировкой,
# если в качестве ключа передавать функцию,
# возвращающую по строке x пару из её длины и самой этой строки
# Такие пары будут сами сравниваться лексикографически
list(sorted(names, key=lambda x: (len(x), x)))
['Аня', 'Оля', 'Юля', 'Маша', 'Петя', 'Света']
# Ещё пример: сортировка без учёта регистра
# По умолчанию заглавные буквы идут раньше
s = "Студенты Факультета компьютерных наук Высшей школы экономики"
words = s.split(" ")
list(sorted(words))
['Высшей', 'Студенты', 'Факультета', 'компьютерных', 'наук', 'школы', 'экономики']
# Будем при сортировке сравнивать строки в нижнем регистре
list(sorted(words, key=str.lower))
['Высшей', 'компьютерных', 'наук', 'Студенты', 'Факультета', 'школы', 'экономики']
# Вспомним теперь функции.
# Напишем функцию проверки простоты числа
def is_prime(x):
if x <= 1:
return False
for d in range(2, int(x**0.5) + 1):
if x % d == 0:
return False
return True
for i in range(15):
print(i, is_prime(i))
0 False 1 False 2 True 3 True 4 False 5 True 6 False 7 True 8 False 9 False 10 False 11 True 12 False 13 True 14 False
# Списковые выражения: описание элементов списка в общем виде
[x**2 for x in range(15)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196]
[(x, is_prime(x)) for x in range(15)]
[(0, False), (1, False), (2, True), (3, True), (4, False), (5, True), (6, False), (7, True), (8, False), (9, False), (10, False), (11, True), (12, False), (13, True), (14, False)]
[x for x in range(15) if is_prime(x)]
[2, 3, 5, 7, 11, 13]
# Если вместо квадратных скобок поставить круглые,
# то получится так называемый "генератор".
# Он не вычисляет все элементы сразу, а умеет выдавать их по очереди
# (например, в цикле)
(x for x in range(15) if is_prime(x))
<generator object <genexpr> at 0x7f73f012f168>
list((x for x in range(15) if is_prime(x)))
[2, 3, 5, 7, 11, 13]
primes = (x for x in range(15) if is_prime(x))
for i in primes:
print(i)
2 3 5 7 11 13
# Мы уже проитерировались по генератору primes, поэтому теперь он пуст
# Про генераторы поговорим подробнее в следующий раз
list(primes)
[]
# Можно еще писать выражения с фигурными скобками
# Получится словарь
{x: is_prime(x) for x in range(15)}
{0: False, 1: False, 2: True, 3: True, 4: False, 5: True, 6: False, 7: True, 8: False, 9: False, 10: False, 11: True, 12: False, 13: True, 14: False}
# Словарь из предыдущего примера не очень интересен,
# так как ключи в нем - подряд идущие неотрицательные целые числа
# Вместо словаря там можно было бы использовать список
# Сделаем что-нибудь поинтереснее:
{x: is_prime(x) for x in range(2, 19) if x % 2 == 1}
{3: True, 5: True, 7: True, 9: False, 11: True, 13: True, 15: False, 17: True}
# Как задать двумерный массив (матрицу)?
# Воспользуемся для этого списком списков,
# задавая матрицу как список строк
A = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
# Обычный вывод на экран печатает все "плоско":
print(A)
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# Напишем для удобства функцию печати матрицы:
def print_matrix(A):
for row in A:
print(row)
print_matrix(A)
[1, 2, 3] [4, 5, 6] [7, 8, 9]
# Правда, если число цифр в элементах разное, то вывод "поплывет":
A[1][1] = -153
print_matrix(A)
[1, 2, 3] [4, -153, 6] [7, 8, 9]
# Напишите сами функцию, печатающую матрицу аккуратно
# Предположим, нам надо транспонировать матрицу.
# Первый способ:
def transpose(A):
A_transp = []
for j in range(len(A[0])): # число столбцов
row_transp = []
for row in A:
row_transp.append(row[j])
A_transp.append(row_transp)
return A_transp
print_matrix(transpose(A))
[1, 4, 7] [2, -153, 8] [3, 6, 9]
# Второй способ - то же самое, но с помощью списковых выражений
def transpose2(A):
return [[row[j] for row in A] for j in range(len(A[0]))]
print_matrix(transpose2(A))
[1, 4, 7] [2, -153, 8] [3, 6, 9]
# Этого же можно добиться с помощью встроенной функции zip:
print_matrix(list(zip(*A)))
(1, 4, 7) (2, -153, 8) (3, 6, 9)
# Функция zip получает на вход несколько коллекций
# и группирует их первые элементы, вторые элементы, и т. д.
list(zip([1,2,3], "abc", [5,6,7,8]))
[(1, 'a', 5), (2, 'b', 6), (3, 'c', 7)]
# Конструкция *A в zip(*A) - это способ передать элементы списка A как отдельные параметры
# Давайте научимся писать функции с переменным числом аргументов (как в zip)
def f(a, b, *c, **d):
print(a) # a и b - обычные обязательные аргументы
print(b)
print(c) # с - это кортеж (быть может пустой) из дополнительных аргументов
print(d) # d - это словарь остальных именованных аргументов
f(1, 2)
1 2 () {}
f(1, 2, 3, 4, 5)
1 2 (3, 4, 5) {}
f(1, 2, 3, 4, 5, x=6, y=7)
1 2 (3, 4, 5) {'x': 6, 'y': 7}
# Если у нас уже есть список или словарь (с текстовыми ключами),
# то их можно "развернуть" и передать каждый их элемент как свой аргумент в функцию
l = [3, 4, 5]
d = {"x": 6, "y": 7}
f(1, 2, *l, **d)
1 2 (3, 4, 5) {'x': 6, 'y': 7}