Страницы

Поиск по вопросам

вторник, 5 февраля 2019 г.

Python: разбить список на список списков, по элементу-разделителю

Python 3.2. Есть список lst, в котором есть значения вперемешку с элементами-разделителями. Например, ["spam", "ham", None, "eggs", None, None, "bacon"]. Хочу получить список списков, разбив lst по разделителю sep = None, т.е., получить [["spam", "ham"], ["eggs"], ["bacon"]] Полистал стандартную библиотеку, но ничего похожего не нашел. На PyPi искать сложно, быстрый пробег тоже ничего не дал. Наглая попытка проэксплуатировать str.split, разумеется, провалилась с TypeError Посоветуйте, пожалуйста, более красивое решение, чем вот этот вырвиглазный монстр. Не хочу ощущать себя Франкенштейном. from functools import reduce
# Fugly. def split_on(sep, lst): """ Given an iterable `lst`, split it into iterable of lists by `sep`.
>>> list(split_on(0, [1, 2, 3, 0, 4, 5, 0, 0, 6])) [[1, 2, 3], [4, 5], [6]] """ s = sep if hasattr(sep, "__call__") else lambda x: x == sep return filter(lambda sublist: len(sublist) > 0, reduce(lambda x, elem: x + [[]] if elem == sep else x[:-1] + [x[-1] + [elem]], lst, [[]]))


Ответ

Судя по всему, функциональное программирование оставило на вас серьезный отпечаток :) Сразу отмечу, что семантика split для случая вашего примера подразумевает возврат [["spam", "ham"], ["eggs"], [], ["bacon"]]. Это так, поскольку между None и None с точки зрения разделителей располагается пустой список. Так вот, решений можно придумать несколько. Наиболее explicit вариант подразумевает что-то в следующем духе: def split_on(what, delimiter = None): splitted = [[]] for item in what: if item == delimiter: splitted.append([]) else: splitted[-1].append(item)
return splitted Понятно, что это решение работает с точностью до контракта функции касательно работы в случае пустого списка - [] и списка, состоящего только из разделителя - [None]. Я определил этот контракт следующим образом: [ ] -> [[ ]], [None] -> [[], []]. Для первого случая контракт довольно спорный. Во втором же случае результат получается, поскольку слева и справа от разделителя по сути расположены пустые последовательности. В случае, если вы захотите изменить это поведение, то модифицировать метод не должно составить особого труда. Пример использования: list1 = ["spam", "ham", None, "eggs", None, None, "bacon"] list2 = [] list3 = [None] list4 = ["eggs"]
print split_on(list1) print split_on(list2) print split_on(list3) print split_on(list4)
# Результат: [['spam', 'ham'], ['eggs'], [], ['bacon']] [[]] [[], []] [['eggs']] Из альтернативных вариантов - можно написать аналогичный предложенной функции генератор с yield'ами и, думаю, что можно придумать решение, разбивая предложенную итерабельную последовательность на группы, а дальше объединяя результаты путем groupby из itertools. Правда, мне кажется, что очевидность этих решений по сравнению с предложенным выше методом будет несколько хуже.

Комментариев нет:

Отправить комментарий