Элегантный способ свернуть или развернуть подпоследовательности списка в Python? - PullRequest
2 голосов
/ 20 апреля 2010

Я хочу свернуть или развернуть подпоследовательности списка

например. ['A', 'B', 'D', 'E', 'H'] -> ['AB', 'DE', 'H'] и наоборот

РЕДАКТИРОВАТЬ: приведенный выше пример может вызвать недоразумение. лучше следующее:

например. ['foo', 'bar', 'wtf'] <-> ['baz', 'wtf']

В настоящее время я написал некрасивый код вроде:

while True:
  for i, x in enumerate(s):
    if x == 'foo' and s[i+1] == 'bar':
      s[i:i+2] = 'baz'
      break
  else:
    break

Для людей, которые спрашивают «зачем это делать»:

На самом деле я работаю над оптимизирующим компилятором, и это часть глазка. Написание сопоставления с образцом немного раздражает.

P.S. Я нашел следующий код работает, но немного нелепо, зачем перечислять знать нашу модификацию?

s = ['foo', 'bar', 'wtf', 'foo', 'bar', 'wtf', 'foo', 'bar', 'wtf']

def collapse():
    for i, x in enumerate(s):
        if s[i] == 'foo' and s[i+1] == 'bar':
            s[i:i+2] = ['baz']

def expand():
    for i, x in enumerate(s):
        if s[i] == 'baz':
            s[i:i+1] = ['foo', 'bar']

collapse()
print s
expand()
print s

Ответы [ 3 ]

2 голосов
/ 20 апреля 2010

Я бы не назвал это намного лучше, но это другой способ сделать это, и он также обрабатывает причуду, на которую указывает Джастин. (Меня больше интересовало нахождение подпоследовательности в списке, и я не смог найти хорошую функцию в Google)

def findsubseq(L, subseq):
    if not subseq: return # just die on zero-len input
    i = -1
    try:
        while True:
            i = L.index(subseq[0], i+1)
            for j in range(1, len(subseq)):
                if L[i+j] != subseq[j]:
                    break
            else:
                yield i
    except ValueError: pass
    except IndexError: pass

def replace(target, changethis, tothis):
    subseqs = [x for x in findsubseq(target, changethis)]
    subseqs.reverse()
    for i in subseqs:
        target[i:i+len(changethis)] = tothis
def collapse():
    global s
    replace(s, ['foo', 'bar'], ['baz'])
def expand():
    global s
    replace(s, ['baz'], ['foo', 'bar'])

s = ['foo', 'bar', 'wtf', 'foo', 'bar', 'wtf',
       'foo', 'bar', 'bar', 'bar', 'foo']
print s
collapse()
print s
expand()
print s

C:\Scripts>subseq.py
['foo', 'bar', 'wtf', 'foo', 'bar', 'wtf', 'foo', 'bar', 'bar', 'bar', 'foo']
['baz', 'wtf', 'baz', 'wtf', 'baz', 'bar', 'bar', 'foo']
['foo', 'bar', 'wtf', 'foo', 'bar', 'wtf', 'foo', 'bar', 'bar', 'bar', 'foo']

Редактировать : обобщено это просто функция замены

1 голос
/ 20 апреля 2010

См. itertools . В частности, вот рецепт для более или менее того, что вы хотите (на самом деле, то, что я думал, что вы хотели после своего вводящего в заблуждение оригинального сообщения!):

from itertools import tee, izip

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return izip(a, b)

Это вернет кортежи, которые вы можете join().

Чтобы отменить это, просто join() ваша последняя последовательность и переберите отдельные элементы (символы).

Я постараюсь найти ответ на ваш новый / реальный вопрос.

0 голосов
/ 20 апреля 2010

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

s = ['foo', 'bar', 'wtf', 'foo', 'bar', 'wtf', 'foo']

тогда последний 'foo', у которого нет 'bar', даст вам исключение, когда ваш код попытается посмотреть на элемент за концом массива. Я пока не знаю, как это исправить, потому что мои попытки не увенчались успехом.

Edit:

Это может быть не очень элегантно, но этот код для функции collapse() будет работать даже в вышеуказанном случае:

def collapse():
    i = 1
    L = len(s)
    while i < L:
        if s[i-1] == 'foo' and s[i] == 'bar':
            s[i-1:i+1] = ['baz']
            L -= 1
        i += 1
...