Сгладить список строк и списков строк и списков в Python

Аналогичные вопросы задавали раньше, но решения для них не работают для моего варианта использования (например, Создание плоского списка из списка списков в Python и Сглаживание мелкого списка в Python. У меня есть список строк и списков, где встроенный список также может содержать строки и списки. Я хочу превратить это в простой список строк без разбиения строк на список символов.

import itertools

list_of_menuitems = ['image10', ['image00', 'image01'], ['image02', ['image03', 'image04']]]
chain = itertools.chain(*list_of_menuitems)

Итоговый список:

['i', 'm', 'a', 'g', 'e', '1', '0', 'image00', 'image01', 'image02', ['image03', 'image04']]

Ожидаемый результат:

['image10', 'image00', 'image01', 'image02', 'image03', 'image04']

Какой лучший (Pythonic) способ сделать это?

Ответ 1

Часто повторяющаяся функция flatten может быть применена к этому обстоятельству с простой модификацией.

from collections import Iterable
def flatten(coll):
    for i in coll:
            if isinstance(i, Iterable) and not isinstance(i, basestring):
                for subc in flatten(i):
                    yield subc
            else:
                yield i

basestring будет гарантировать, что объекты str и unicode не будут разделены.

Существуют также версии, которые рассчитываются на i, не имеющих атрибута __iter__. Я не знаю обо всем этом, потому что я думаю, что str теперь имеет этот атрибут. Но, стоит упомянуть.

(Пожалуйста, поддержите связанный ответ.)

Ответ 2

Использование рекурсии.

def flatten(A):
    rt = []
    for i in A:
        if isinstance(i,list): rt.extend(flattern(i))
        else: rt.append(i)
    return rt

Тестовое задание:

>>> list_of_menuitems = ['image10', ['image00', 'image01'], ['image02', ['image0
3', 'image04']]]
>>> flattern(list_of_menuitems)
['image10', 'image00', 'image01', 'image02', 'image03', 'image04']

Ответ 3

Следующие строки для строк (и будут легко адаптированы к другим типам):

def flatten_to_strings(listOfLists):
    """Flatten a list of (lists of (lists of strings)) for any level 
    of nesting"""
    result = []

    for i in listOfLists:
        # Only append if i is a basestring (superclass of string)
        if isinstance(i, basestring):
            result.append(i)
        # Otherwise call this function recursively
        else:
            result.extend(flatten_to_strings(i))
    return result

flatten_to_strings(list_of_menuitems)
Out[2]: ['image10', 'image00', 'image01', 'image02', 'image03', 'image04']

Ответ 4

В одном специализированном случае, когда ни один из элементов списка не содержит один из следующих разделителей []', вы можете использовать следующий хак. Я не профилировал его, но очевидно, что это будет иметь лучшую производительность, чем очевидное и более чистое рекурсивное решение.

>>> str(list_of_menuitems).translate(None,"[]'").split(',')
['image10', ' image00', ' image01', ' image02', ' image03', ' image04']

Я согласен, это грязный взлом, но выполняет JOB без особых усилий.

Ответ 5

Это общий рекурсивный сплюс, который может использоваться для работы с любой комбинацией типов, которые должны или не должны быть сплющены:

import collections
def generic_flatten(seq, flatten_types=(tuple,list,set),atom_types=(basestring,dict),fixtype=True):
    newseq = []
    for item in seq:
        if (not isinstance(collections.Iterable)) or any(isinstance(i,t) for t in atom_types):
           newseq.append(item)
        elif any(isinstance(i,t) for t in flatten_types): # set flatten_types to (object,) or (collections.Iterable,) to disable check
           newseq.extend(generic_flatten(item, flatten_types, atom_types,fixtype)
    if fixtype and type(newseq) is not type(seq):
       newseq = type(seq)(newseq)
    return newseq

yield и chain могут использоваться для создания общей версии, основанной на итераторе.