Итераторы. Строки и списки
Напоминание
Для понимания материала ОБЯЗАТЕЛЬНО каждый пример пытаться исполнить, понять, почему он работает или не работает, и попробовать в нём что-то поменять, и посмотреть, что получится.
Программирование – это практическая дисциплина, и без успешных попыток что-то сделать руками понимания теории не появляется.
Разбор задач
- Задача
- Напишите программу, которая считает среднее значение числа в списке. Проверьте её работу на списках: [1, 2, 3], [4], []
Начнём с определения списка:
1 list = [1, 2, 3]
Среднее – это сумма элементов, делённая на их количество. Значит, первым делом нам нужно посчитать сумму элементов. Это можно сделать способом, очень похожим на то, как мы в первом задании считали номер строки (только на каждом шагу мы прибавляем к счётчику не единицу, а величину следующего элемента):
Теперь мы можем посчитать и среднее:
1 print "Average:", sum / len(list)
- Задача
- Напишите программу, которая собирает в списке list_reversed элементы списка list в обратном порядке.
Начало нам предопределено (с точностью до наполнения списка list):
1 list = [1, 4, 2, 5]
Теперь нам нужно в list_reversed положить элементы списка list в обратном порядке. В обратном порядке, то есть list_reversed[i] = list[-i - 1]. Казалось бы, можно было бы так и написать, но натыкаемся на такую трудность: мы не можем менять содержимое списка за рамками границ списка. То есть расширять границы списка конструкцией list_reversed[i] = ... у нас не получится. Как быть? Есть два варианта: либо добиться того, чтобы эта операция не была расширением списка (то есть он изначально должен быть нужного размера, например: list_reversed = [0] * len(list)), либо нужно воспользоваться операцией добавления элемента в список – методом append (но в этом случае нам тоже нужно, чтобы вначале в переменной list_reversed уже лежал какой-то список, притом в нём не было бы ничего лишнего – то есть не было бы ничего вообще):
Получаются варианты. Вариант 1:
Вариант 2:
Вариант 3:
Ну и в конце мы можем вывести наши достижения:
1 print list, "reversed will get you", list_reversed
- Задача
- Напишите программу, которая проверяет, является ли данный список list палиндромом. (Программа начинается со строки list = [...], и пишет ответ вида "List [1, 2, 3, 2, 1] is a palindrome"). Проверьте её на нескольких разных списках. Может ли она проверить на палиндромность строку?
первая строка программы нам уже дана:
1 list = [1, 2, 3, 2, 1]
дальше нам нужно вспомнить определение палиндрома: это строка, которая читается спереди так же, как и сзади. Проще всего эту задачу свести к предыдущей: палиндром – это такая строка, для которой сама строка равна развёрнутой. Если мы воспользуемся частью прошлого решения, то получится:
elif
Вместо:
Можно (и нужно!) писать так:
Итераторы
Когда мы писали песенку про 99 бутылок, нам хотелось бы идти по списку 0..99 в обратном порядке. Мы обошлись без этого, но это можно сделать: функция range() может принимать до трёх аргументов, точно так же, как и слайсы в списках:
Результат последней команды наверняка вас удивил. Но на самом деле всё очевидно: здесь действует то же правило, что и раньше – мы всегда останавливаемся непосредственно перед последним элементом. Но в этом случае приходится долго и напряжённо думать, как же именно задать условия, чтобы получить именно тот диапазон, с которым мы хотим работать. Поэтому этот способ я не рекомендую.
А более простой способ пройти от 99 до 0 – использовать итератор reversed(). Итератор – это такой объект, который ведёт себя как список, если его подсунуть в цикл for, но во всех остальных применениях совершенно несъедобен1:
Если же нам нужно всё-таки превратить то, что вернул итератор, в список, то для этого нужно воспользоваться функцией list – она пытается делать список из всего, что ей дадут в качестве аргумента:
1 print list(reversed(range(10)))
В первую очередь итераторы придумали для того, чтобы экономить время и память (но нам о таких вещах пока что думать не имеет смысла). Однако мы ещё увидим (где-нибудь в третьем модуле), что итераторы могут очень здорово упрощать жизнь – когда мы научимся делать специальные итераторы для наших задач.
Сейчас же нам легко понять пользу от итераторов вот как: в питоне есть итератор, который идёт по бесконечному списку. (Мы не можем просто взять и положить бесконечный список в память – поэтому без итераторов тут не обойтись). Такие итераторы (а ещё куча других полезных и других бесполезных) лежит в модуле itertools. Нашу задачку с вертушкой мы могли бы решить так2:
А можете ли вы угадать, что делают:
Разбор строк
И о том, как мы можем себе немножко упростить вытаскивание полезной информации из строк.
Самый частый вид полезной информации – список слов в строке. Делает это метод split у строк:
Питон тут понимает слово как любой набор видимых символов, идущих подряд (то есть, всё кроме пробелов, табуляций, переносов строки). Как следствие, если в строке идёт подряд несколько пробелов, питон их считает за один разделитель между слов.
У функции split есть второе полезное назначение, с которым мы познакомимся ближе в следующий раз: разбивать строку по заданному разделителю:
В этом случае питон выдаст нам список всех фрагментов строки между разделителями – и тут оказывается, что две запятые подряд ограничивают ещё один фрагмент строки, который питон считает существенным нам выдать. (Вскоре увидим пользу от такого решения).
В этом примере мы увидели, что на этот раз питон оставил в строке все пробелы, которые были (они же не являются уже разделителями!). И это выглядит не симпатично, а в некоторых случаях может и вовсе помешать последующей обработке.
Для борьбы с этим делом в питоне есть метод строк strip, который отрезает все лишние пробелы по краям строки (а с ними заодно и переносы строк и табуляции – все такие символы, которые видны как просто пустое белое пространство, называются whitespace – пробельными символами):
И, соответственно:
У метода strip есть ещё два собрата: методы lstrip и rstrip, которые убирают лишние пробелы только слева или только справа соответственно.
Наконец, у метода split есть антипод, который собирает обратно строку из списка её кусочков, и вставляет между ними разделитель. Называется join:
В питоне есть золотое правило: B.join(A.split(B)) == A. То есть если мы разбили строку A по разделителю B, а потом собрали кусочки, вставив между ними разделитель B, то результат будет снова идентичным.
У метода join есть одна маленькая, но иногда очень противная беда: он требует на вход обязательно список только из строк. Когда мы с ней столкнёмся, нам придётся научиться превращать список чего угодно (например, чисел) в список строк. (Вдумчивый читатель может догадаться, как это сделать, уже сейчас – точно так же, как мы использовали strip для того, чтобы убрать лишние пробелы с каждого элемента списка).
Литература
http://docs.python.org/2.7/tutorial/introduction.html#strings
http://docs.python.org/2.7/library/stdtypes.html#string-methods
http://docs.python.org/2.7/tutorial/classes.html#iterators – только первый абзац текста
Я сказал "итератор reversed(), но если говорить совсем формально, то reversed() -- это функция, которая берёт на вход список, и возвращает итератор, обходящий элементы этого списка в обратном порядке. (1)
И исключительно по моему недосмотру это не тот вариант, которым я предложил это делать на первом занятии (2)