Подтвердить что ты не робот

Большинство Pythonic способ разделить массив по повторяющимся элементам

У меня есть список элементов, которые я хочу разбить на основе разделителя. Я хочу, чтобы все разделители были удалены, и список, который нужно разделить, когда разделить разделитель дважды. Например, если разделитель 'X', то следующий список:

['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g']

Повернулся бы:

[['a', 'b'], ['c', 'd'], ['f', 'g']]

Обратите внимание, что последний набор не разбит.

Я написал какой-то уродливый код, который делает это, но я уверен, что есть что-то приятнее. Дополнительные точки, если вы можете установить произвольный разделитель длины (т.е. Разделите список после просмотра N разделителей).

4b9b3361

Ответ 1

Я не думаю, что это будет хорошее, элегантное решение для этого (я бы хотел, чтобы это было неправильно, конечно), поэтому я бы предложил что-то прямое:

def nSplit(lst, delim, count=2):
    output = [[]]
    delimCount = 0
    for item in lst:
        if item == delim:
            delimCount += 1
        elif delimCount >= count:
            output.append([item])
            delimCount = 0
        else:
            output[-1].append(item)
            delimCount = 0
    return output

 

>>> nSplit(['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g'], 'X', 2)
[['a', 'b'], ['c', 'd'], ['f', 'g']]

Ответ 2

Здесь можно сделать это с помощью itertools.groupby():

import itertools

class MultiDelimiterKeyCallable(object):
    def __init__(self, delimiter, num_wanted=1):
        self.delimiter = delimiter
        self.num_wanted = num_wanted

        self.num_found = 0

    def __call__(self, value):
        if value == self.delimiter:
            self.num_found += 1
            if self.num_found >= self.num_wanted:
                self.num_found = 0
                return True
        else:
            self.num_found = 0

def split_multi_delimiter(items, delimiter, num_wanted):
    keyfunc = MultiDelimiterKeyCallable(delimiter, num_wanted)

    return (list(item
                 for item in group
                 if item != delimiter)
            for key, group in itertools.groupby(items, keyfunc)
            if not key)

items = ['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g']

print list(split_multi_delimiter(items, "X", 2))

Я должен сказать, что решение cobbal намного проще для тех же результатов.

Ответ 3

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

l = ['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g'] 

def splitOn(ll, x, n):
    cur = []
    splitcount = 0
    for c in ll:
        if c == x:
            splitcount += 1
            if splitcount == n:
                yield cur
                cur = []
                splitcount = 0
        else:
            cur.append(c)
            splitcount = 0
    yield cur

print list(splitOn(l, 'X', 2))
print list(splitOn(l, 'X', 1))
print list(splitOn(l, 'X', 3))

l += ['X','X']
print list(splitOn(l, 'X', 2))
print list(splitOn(l, 'X', 1))
print list(splitOn(l, 'X', 3))

печатает:

[['a', 'b'], ['c', 'd'], ['f', 'g']]
[['a', 'b'], [], ['c', 'd'], [], ['f'], ['g']]
[['a', 'b', 'c', 'd', 'f', 'g']]
[['a', 'b'], ['c', 'd'], ['f', 'g'], []]
[['a', 'b'], [], ['c', 'd'], [], ['f'], ['g'], [], []]
[['a', 'b', 'c', 'd', 'f', 'g']]

EDIT: Я тоже большой поклонник groupby, здесь я нахожусь на нем:

from itertools import groupby
def splitOn(ll, x, n):
    cur = []
    for isdelim,grp in groupby(ll, key=lambda c:c==x):
        if isdelim:
            nn = sum(1 for c in grp)
            while nn >= n:
                yield cur
                cur = []
                nn -= n
        else:
            cur.extend(grp)
    yield cur

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

Ответ 4

a = ['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g']
b = [[b for b in q if b != 'X'] for q in "".join(a).split("".join(['X' for i in range(2)]))]

это дает

[['a', 'b'], ['c', 'd'], ['f', 'g']]

где 2 - количество требуемых элементов. скорее всего, лучший способ сделать это.

Ответ 5

Очень уродливо, но я хотел посмотреть, смогу ли я это сделать как однострочный, и я подумал, что поделюсь. Я прошу вас не использовать это решение для чего-либо важного. ('X', 3) в конце - это разделитель и количество повторений.

(lambda delim, count: map(lambda x:filter(lambda y:y != delim, x), reduce(lambda x, y: (x[-1].append(y) if y != delim or x[-1][-count+1:] != [y]*(count-1) else x.append([])) or x, ['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g'], [[]])))('X', 2)

ИЗМЕНИТЬ

Здесь разбивка. Я также устранил некоторый избыточный код, который был гораздо более очевидным, когда он был написан таким образом. (также изменено выше)

# Wrap everything in a lambda form to avoid repeating values
(lambda delim, count:
    # Filter all sublists after construction
    map(lambda x: filter(lambda y: y != delim, x), reduce(
        lambda x, y: (
            # Add the value to the current sub-list
            x[-1].append(y) if
                # but only if we have accumulated the
                # specified number of delimiters
                y != delim or x[-1][-count+1:] != [y]*(count-1) else

                # Start a new sublist
                x.append([]) or x,
        ['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g'], [[]])
    )
)('X', 2)

Ответ 6

Здесь чистое красивое решение с использованием zip и генераторов

#1 define traditional sequence split function 
#if you only want it for lists, you can use indexing to make it shorter
def split(it, x):
    to_yield = []
    for y in it:
        if x == y:
            yield to_yield
            to_yield = []
        else:
            to_yield.append(y)
    if to_yield:
        yield to_yield

#2 zip the sequence with its tail 
#you could use itertools.chain to avoid creating unnecessary lists
zipped = zip(l, l[1:] + [''])

#3. remove ('X',not 'X') from the resulting sequence, and leave only the first position of each
# you can use list comprehension instead of generator expression
filtered = (x for x,y in zipped if not (x == 'X' and y != 'X'))

#4. split the result using traditional split
result = [x for x in split(filtered, 'X')]

Этот способ split() более многоразовый.

Удивительный python не имеет встроенного.

изменить:

Вы можете легко отрегулировать его для более длинных разделяемых последовательностей, повторяя шаги 2-3 и зачистив фильтр с помощью l [i:] для 0 < я <= n.

Ответ 7

import re    
map(list, re.sub('(?<=[a-z])X(?=[a-z])', '', ''.join(lst)).split('XX'))

Это преобразование списка → string → list и предполагает, что символы без разделителя - все строчные буквы.

Ответ 8

Вот еще один способ сделать это:

def split_multi_delimiter(items, delimiter, num_wanted):
    def remove_delimiter(objs):
        return [obj for obj in objs if obj != delimiter]

    ranges = [(index, index+num_wanted) for index in xrange(len(items))
              if items[index:index+num_wanted] == [delimiter] * num_wanted]

    last_end = 0
    for range_start, range_end in ranges:
        yield remove_delimiter(items[last_end:range_start])
        last_end = range_end

    yield remove_delimiter(items[last_end:])

items = ['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g']
print list(split_multi_delimiter(items, "X", 2))

Ответ 9

In [6]: input = ['a', 'b', 'X', 'X', 'cc', 'XX', 'd', 'X', 'ee', 'X', 'X', 'f']

In [7]: [s.strip('_').split('_') for s in '_'.join(input).split('X_X')]
Out[7]: [['a', 'b'], ['cc', 'XX', 'd', 'X', 'ee'], ['f']]

Предполагается, что вы можете использовать зарезервированный символ, например _, который не найден на входе.

Ответ 10

Слишком умно пополам и предлагается только потому, что очевидный правильный способ сделать это кажется настолько грубым и уродливым:

class joiner(object):
  def __init__(self, N, data = (), gluing = False):
    self.data = data
    self.N = N
    self.gluing = gluing
  def __add__(self, to_glue):
    # Process an item from itertools.groupby, by either
    # appending the data to the last item, starting a new item,
    # or changing the 'gluing' state according to the number of
    # consecutive delimiters that were found.
    N = self.N
    data = self.data
    item = list(to_glue[1])
    # A chunk of delimiters;
    # return a copy of self with the appropriate gluing state.
    if to_glue[0]: return joiner(N, data, len(item) < N)
    # Otherwise, handle the gluing appropriately, and reset gluing state.
    a, b = (data[:-1], data[-1] if data else []) if self.gluing else (data, [])
    return joiner(N, a + (b + item,))

def split_on_multiple(data, delimiter, N):
  # Split the list into alternating groups of delimiters and non-delimiters,
  # then use the joiner to join non-delimiter groups when the intervening
  # delimiter group is short.
  return sum(itertools.groupby(data, delimiter.__eq__), joiner(N)).data

Ответ 11

Regex, я выбираю вас!

import re

def split_multiple(delimiter, input):
    pattern = ''.join(map(lambda x: ',' if x == delimiter else ' ', input))
    filtered = filter(lambda x: x != delimiter, input)
    result = []
    for k in map(len, re.split(';', ''.join(re.split(',',
        ';'.join(re.split(',{2,}', pattern)))))):
        result.append([])
        for n in range(k):
            result[-1].append(filtered.__next__())
    return result

print(split_multiple('X',
    ['a', 'b', 'X', 'X', 'c', 'd', 'X', 'X', 'f', 'X', 'g']))

О, вы сказали Python, а не Perl.