LINUX.ORG.RU

[python] [итераторы] [генераторы] Вернуть генератор из рекурсивной функции

 


0

2

Есть одна задача и решается она рекурсивным перебором. Вкратце так - на вход поступает одна строка, а на выходе большое множество строк (все возможные комбинации). Подробнее объяснять не буду, незачем, а если кому вдруг интересно - можете почитать мои предыдущие треды, там это можно найти.

Так вот... рекурсивный перебор я сделал. Вот кусок кода:

def backtrack(currentBytes, k):
    if k == l - 1:
        for x in table[s1[k]]:
            print(currentBytes + x + b'\n')
        return
    elif k < l - 1:
        for x in table[s1[k]]:
            backtrack(currentBytes + x, k + 1)

backtrack(b'', 0)

В общем, суть такова - backtrack - рекурсивная функция, которая и перебирает все возможные комбинации. В строке, где вызывается print() и выводится очередная результативная строка... выводится в стандартный поток или в файл, не важно. После вывода (он в цикле) осуществляется возврат.

Так вот... мне бы хотелось переделать эту функцию так, чтобы вместо вывода (в файл, в стандартный поток или ещё куда), чтобы она стала генератором. То есть, чтобы сразу при выводе она не вычислялась полностью, а только на один шаг... ну что такое генераторы, думаю, python-гуру знают =)

Пробовал заменить print() на yield - но это не помогло... Как всё-таки правильно сделать? %)

★★★★★

Последнее исправление: BattleCoder (всего исправлений: 1)

Достать yeild-у.

anonymous
()

Пробовал заменить print() на yield - но это не помогло...

Ой, надо дочитывать. А привести к виду без print (заменив на return)?

anonymous
()

Подробнее объяснять не буду, незачем, а если кому вдруг интересно - можете почитать мои предыдущие треды, там это можно найти.

Эта задача решается очень просто, подробнее объяснять не буду, не зачем, если интересно можете почитать М.Лутца «программирование на питон»

Вот кусок кода:

что такое table, s1, l?

AIv ★★★★★
()

Почему-бы не сгенерировать список и вернуть его? Или надо именно православный генератор?

Dragon59 ★★
()
Ответ на: комментарий от Dragon59

TC очевидно думает, что ему не хватает памяти и генератор его спасет. Он еще не знает, что перед оптимизацией надо делать профилирование.

AIv ★★★★★
()
Ответ на: комментарий от anonymous

А не получится... функция рекурсивная фактически не возвращает результат (она void, говоря терминами других ЯП). она в цикле for печатает множество возвращаемых значений...

вопрос - как мне её переделать? :)

BattleCoder ★★★★★
() автор топика

А что значит «не помогло»?

tailgunner ★★★★★
()
Ответ на: комментарий от AIv

А это так важно? :) table - словарь, s1 строка или список (сам уж не помню), l - число.

Объясню более отвлечённо (чтобы не забивать голову всякими лишними переменными)

Есть одна функция, внутри неё другая. вторая рекурсивная и вызывается из первой (один раз).

требуется, чтобы внешняя функция была генератором.

Почему-бы не сгенерировать список и вернуть его? Или надо именно православный генератор?

Оперативной памяти не хватит. Список чуть более чем до-хрена-громадный.

TC очевидно думает, что ему не хватает памяти и генератор его спасет. Он еще не знает, что перед оптимизацией надо делать профилирование.

Наивно думает? Типа не поможет?

BattleCoder ★★★★★
() автор топика
Ответ на: комментарий от zz

Ммм. мне не совсем понятно, что делает product, и коим боком он тут мне пригодится. :)

Что мне нужно вписать вместо «abcd» и в repeat=?

Ааа. я понял. Нет, к сожалению, этот вариант мне не совсем подойдёт... он перебирает все комбинации из символов abcd... а у меня на каждой позиции могут быть РАЗНЫЕ символы. И не просто могут быть, а так всегда и есть. :( к сожалению.

именно эти символы я беру из таблицы table

Знаю, что имена переменных надо получше придумывать =) эт я потом поменяю. мне бы сначала понять, как сделать так, чтобы работало...

BattleCoder ★★★★★
() автор топика

Формулирую задачу ещё раз.

Итак, есть внешняя функция func1, внутри неё func2. вторая рекурсивная.

def func1:
    <предварительные действия> func2(0)

def func2(number):
    if <условие>:
        for x in <список>:
            print(<РЕЗУЛЬТАТ, зависящий от x>)
        return
    else:
        for x in <список>:
            func2(number+1)

И надо этот print заменить на yield... но чтобы он относился к функции func2, то есть чтобы она возвращала итерируемый объект. Да, и будет ли это экономить память, если список длиной более 100500 строк, или нет? если нет, то почему?

Либо привести эти две функции к другому виду... В качестве результат мне нужен генератор, чтобы я мог сделать что-то вроде

generator = func1()
for x in generator:
    file.write(x)
    file.write('\n')

Например. Как-то так.

BattleCoder ★★★★★
() автор топика
Ответ на: комментарий от BattleCoder

Чёрт, снова строку забыл перевести в одном месте... Что-то уже надоело удалять сообщения и снова вставлять. почему их тут редактировать нельзя?..

В общем, во второй строке func2(0) на новую строку... а так вроде всё правильно.

BattleCoder ★★★★★
() автор топика

Рекурсивную функцию нужно привести к интерируемому виду, и тогда смело заменять print на yield.

FANATID
()
Ответ на: комментарий от FANATID

да это понятно... понять бы как привести к итерируемому виду %)

а без этого точно никак нельзя?..

эх, ну тогда буду думать %)

BattleCoder ★★★★★
() автор топика

Примерно так

def backtrack(currentBytes, k):
    stack = [[currentBytes, k, 0, len(table[s1[k]])]]
    while stack:
        if stack[-1][2] == stack[-1][3]:
            stack.pop()
            continue
        if stack[-1][1] == l - 1:
            for x in table[s1[stack[-1][1]]]:
                yield (stack[-1][0] + x + b'\n')
            stack.pop()
            continue
        stack.append([currentBytes + table[s1[k]][stack[-1][2]], stack[-1][1] + 1, 0, len(table[s1[stack[-1][1] + 1]])])
        stack[-2][2] += 1
FANATID
()
Ответ на: комментарий от BattleCoder

Ааа, понял. code=python наверное...

даже не знал об этом =) терь буду знать.

BattleCoder ★★★★★
() автор топика
Ответ на: комментарий от BattleCoder

чуть более чем дохрена громадный это конечно ааафигительная оценка!;-)

число строк, длина строк, доступный объем памяти какие?

генератор конечно экономит память (если все прально делать). Но для начала настоятельно рекомендую сформулировать задачу человеческим языком. Потому что напр генераторы можно клепать и без елды.

AIv ★★★★★
()
Ответ на: комментарий от FANATID

Именно в таком виде код не работает... ругается на исключение - неправильный индекс. в строке, где stack.append()

Но вообще принцип я понял... вместо рекурсии итерация. в принципе мне это и надо... всяко эффективнее будет =)

можете вкратце объяснить, что записывается в стек и для чего он нужен?

BattleCoder ★★★★★
() автор топика
Ответ на: комментарий от AIv

Попробую. Не хотел этого делать, чтобы не забивать форумцам голову =) ибо по сути итератор - только часть задачи.

s1 - это исходная цепочка символов. Почему такое странное имя у переменной, лучше не придумал =) потом сделаю рефакторинг, в данный момент для меня важнее, чтобы просто работало.

цепочка состоит из символов, ключи которой находятся в таблице table:

table = {ord('A'): (b'GCU', b'GCC', b'GCA', b'GCG'),
                 ord('R'): (b'CGU', b'CGC', b'CGA', b'CGC', b'AGA', b'AGG'),
                 ord('N'): (b'AAU', b'AAC'),
                 ord('D'): (b'GAU', b'GAC'),
                 ord('C'): (b'UGU', b'UGC'),
                 ord('Q'): (b'CAA', b'CAG'),
                 ord('E'): (b'GAA', b'GAG'),
                 ord('G'): (b'GGU', b'GGC', b'GGA', b'GGG'),
                 ord('H'): (b'CAU', b'CAC'),
                 ord('I'): (b'AUU', b'AUC', b'AUA'),
                 ord('L'): (b'UUA', b'UUG', b'CUU', b'CUC', b'CUA', b'CUG'),
                 ord('K'): (b'AAA', b'AAG'),
                 ord('M'): (b'AUG'),
                 ord('F'): (b'UUU', b'UUC'),
                 ord('P'): (b'CCU', b'CCC', b'CCA', b'CCG'),
                 ord('S'): (b'UCU', b'UCC', b'UCA', b'UCG', b'AGU', b'AGC'),
                 ord('T'): (b'ACU', b'ACC', b'ACA', b'ACG'),
                 ord('W'): (b'UGG'),
                 ord('Y'): (b'UAU', b'UAC'),
                 ord('V'): (b'GUU', b'GUC', b'GUA', b'GUG'),
                 ord(' '): (b'UAG', b'UGA', b'UAA') }

Соответственно, значение каждого ключа - это кортеж. Кортеж может быть разной длины (от 1 до кажется около 6). Мне нужно перебрать все возможные значения кортежа (а это цепочки из 3-х символов). И записать в результативную цепочку.

Соответственно, таких цепочек дохрена-много получается =) комбинаторика. полный перебор с возвратом.

Вот почему я хочу сделать итератор. Наиболее простой вариант перебора - это рекурсия, и да, неплохо от неё избавиться =) что я и попытаюсь сейчас сделать по совету товарища FANATID

BattleCoder ★★★★★
() автор топика
Ответ на: комментарий от BattleCoder
from itertools import product

map(lambda x: "".join(x), product("abcd", "efgh"))
zz ★★★★
()
Ответ на: комментарий от BattleCoder
from itertools import product

table = {'a': ('ab', 'cd'),
  'b': ('ef','gh', 'ij'),
  'c': ('klm',)}

def func(str):
  seqs = [table[x] for x in str]
  return product(*seqs)

for seq in func('cbac'):
  print seq
zz ★★★★
()
Ответ на: комментарий от zz

ооо. спс. так немного понятнее =)

и кода намного меньше... чем в старом варианте.

попробую так переделать. ещё раз спасибо =) жалко по лору нельзя пряники высылать на почту

BattleCoder ★★★★★
() автор топика
Ответ на: комментарий от BattleCoder

Уже что то. К мол биологии не имеет отношения?;-)

zz оч. хороший вариант предложил, хотя я бы переписал его короче:

func = lambda s : product(*[ table[x] for x in s ])

Сколько символов в s1 (по макс) и на какой машине это будет крутиться?

AIv ★★★★★
()
Ответ на: комментарий от AIv

А вот пока не могу на такой вопрос ответить... мне пока самому непонятно...

вообще может быть очень много... так как если брать цепочку из 20-30 символов, уже получается очень много... я запускал (старый вариант), с рекурсией... кучу памяти кушало =) и довольно долго работало.

попробую доработать этот вариант и посмотрю, что получится.

Тут ещё если результаты хранить в текстовом файле, то они тоже занимают место, и время на их запись уходит. возможно, их записывать нет смысла, а сразу _порциями_ обрабатывать. как именно их обрабатывать, я пока не знаю точно =) пока над этим тоже думаю.

BattleCoder ★★★★★
() автор топика
Ответ на: комментарий от AIv

Уже что то. К мол биологии не имеет отношения?;-)

Ну вроде как да... в качестве ключей аминокислоты (закодирована одной буквой), а значения - это возможные триплеты РНК.

Решил, что лучше будет кодировать даже не буквой, а числом... вроде памяти должно чуть меньше кушать.

До этого писал на C, используя операторы if-else. И получался if-hell. В общем, словарём намного удобнее и очевиднее. Думаю, на C-ях тоже можно что-то подобное соорудить.

BattleCoder ★★★★★
() автор топика
Ответ на: комментарий от BattleCoder

Уфф... 6^30 это и правда ОЧЕНЬ много, и тут уже никакой генератор не поможет - время прохода по генератору будет ОЧЕНЬ большим. Надо все таки четче поставить задачу, что потом с этой информацией делать? И от этого уже плясать.

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

Генераторы на производительность я не тестил, м.б. то что product возвращает и не тормозит особо по сравнению с С.

AIv ★★★★★
()
Ответ на: комментарий от AIv

6^30 это и правда ОЧЕНЬ много, и тут уже никакой генератор не поможет - время прохода по генератору будет ОЧЕНЬ большим

Кажется, здесь пытаются сэкономить память.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

то, что 6^30 в память не влезет понятно. Непонятно что вообще с такой последовательностью делать - даже если на позицию будет уходить один такт, на проход уйдет 7млн лет. Так что нужно четче формулировать задачу;-)

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

AIv ★★★★★
()
Ответ на: комментарий от AIv

по-любому перебор надо как-то сокращать.. какие-то из комбинаций отбрасывать. =) пока не знаю как.

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

тут больше пользы в том, что подковываю хоть немного навыки программирования на python, чем в чём-то другом на самом деле... ибо миллионов лет у меня, к сожалению, нету =)

BattleCoder ★★★★★
() автор топика
Ответ на: комментарий от BattleCoder

по-любому перебор надо как-то сокращать.. какие-то из комбинаций отбрасывать

Я так понимаю, это как раз и есть наука и креатифф, нужно же предметную область знать. А программирование чего... обезьянья работа, дали мяч - бери фигачь;-)

AIv ★★★★★
()
Ответ на: комментарий от zz

Поменял. В общем, всё замечательно работает, как надо =) и главное очевидно и понятно. Полезный пакет itertools - там забита рутинная работа многих программистов, я так понимаю =)

Один нюанс. Мне в качестве результата надо выводить на кортеж, а строку. от есть мне нужно получающийся кортеж наподобие ('a','b','c') выводить в строку 'abc' ну или b'abc', тут принцип одинаковый.

Как эффективнее всего слить это в одну строку? я сделал вот так:

from functools import reduce
seqs = [table[x] for x in s1]
prod = product(*seqs)
for current in prod:
    yield reduce(lambda x,y: x+y, current)

То есть использовал reduce из functools.

BattleCoder ★★★★★
() автор топика
Ответ на: комментарий от BattleCoder

Ну или что там тебе надо объединить в строку.

PolarFox ★★★★★
()
Ответ на: комментарий от PolarFox

ого... а ларчик, оказывается, просто открывался =) поменяю (а то меня терзают смутные сомнения, что reduce может оказаться более медленным...

BattleCoder ★★★★★
() автор топика
Ответ на: комментарий от BattleCoder

Ага... прирост ощутимый.

при использовании reduce:

real    0m36.362s
user    0m25.769s
sys     0m9.746s

при использовании join:

real    0m19.680s
user    0m13.150s
sys     0m6.276s

Хотя конечно правильнее и точнее было бы измерять в питон-коде, а не bash-скриптом =) но тут прирост итак громадный, итак понятно... вообще конечно профилированием тоже займусь потом =) тем более никогда подобным до этого серьёзно не занимался.

BattleCoder ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.