Шаг 17.
Python: сборник рецептов.
Структуры данных и алгоритмы. Фильтрация элементов последовательности

    На этом шаге мы рассмотрим решение этой задачи.

Задача

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

Решение

    Самый простой способ фильтрования последовательности - использовать генератор списка (list comprehension). Например:

>>> mylist = [1, 4, -5, 10, -7, 2, 3, -1]
>>> [n for n in mylist if n > 0]
[1, 4, 10, 2, 3]
>>> [n for n in mylist if n < 0]
[-5, -7, -1]
>>>

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

>>> pos = (n for n in mylist if n > 0)
>>> pos
<generator object <genexpr> at 0x0000020B90416740>
>>> for x in pos: print(x)

1
4
10
2
3
>>>

    Иногда критерий фильтрования не может быть легко выражен в форме генератора списка или выражения-генератора. Предположим, например, что процесс фильтрации включает обработку исключений или какой-то другой сложный момент. Чтобы справиться с этим, поместите фильтрующий код в функцию и используйте встроенную функцию filter(). Например:

>>> values = ['1', '2', '-3', '-', '4', 'N/A', '5']
>>> def is_int(val):
	try:
		x = int(val)
		return True
	except ValueError:
		return False

	
>>> ivals = list(filter(is_int, values))
>>> print(ivals)
['1', '2', '-3', '4', '5']
>>> 

    filter() создает итератор, так что если вы хотите получить список результатов, не забудьте использовать list(), как показано выше.

Обсуждение

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

>>> mylist = [1, 4, -5, 10, -7, 2, 3, -1]
>>> import math
>>> [math.sqrt(n) for n in mylist if n > 0]
[1.0, 2.0, 3.1622776601683795, 1.4142135623730951, 1.7320508075688772]
>>>

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

>>> clip_neg = [n if n > 0 else 0 for n in mylist]
>>> clip_neg
[1, 4, 0, 10, 0, 2, 3, 0]
>>> clip_pos = [n if n < 0 else 0 for n in mylist]
>>> clip_pos
[0, 0, -5, 0, -7, 0, 0, -1]
>>>

    Другой важный инструмент для фильтрации - itertools.compress(), который принимает итерируемый объект вместе с последовательностью-селектором из булевых значений. На выходе функция выдает все элементы итерируемого объекта, для которых совпадающий элемент в селекторе - True. Это может быть полезно, если вы пытаетесь применить результаты фильтрации одной последовательности к другой связанной последовательности. Например, у вас есть две колонки данных:

>>> addresses = [
	'5412 N CLARK',
	'5148 N CLARK',
	'5800 E 58TH',
	'2122 N CLARK',
	'5645 N RAVENSWOOD',
	'1060 W ADDISON',
	'4801 N BROADWAY',
	'1039 W GRANVILLE',
	]
>>> counts = [0, 3, 10, 4, 1, 7, 6, 1]

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

>>> from itertools import compress
>>> more5 = [n > 5 for n in counts]
>>> more5
[False, False, True, False, False, True, True, False]
>>> list(compress(addresses, more5))
['5800 E 58TH', '1060 W ADDISON', '4801 N BROADWAY']
>>> 

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

    Как и filter(), функция compress() возвращает итератор. Поэтому если вы хотите на выходе получить список, вам придется использовать list().

    На следующем шаге мы рассмотрим извлечение подмножества из словаря.




Предыдущий шаг Содержание