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. Правда, мне кажется, что очевидность этих решений по сравнению с предложенным выше методом будет несколько хуже.