LINUX.ORG.RU

Как в Python написать цикл с постусловием?

 , ,


0

2

Классический цикл for(инит; действие при каждой итерации; условие продолжения) в C/C++ - это, по-сути цикл с пост-условием.

А как написать for-цикл с пост-условием в Python? Если пользоваться range(), то range() «запекает» перебираемые значения при старте цикла.

А нужно, чтобы цикл бежал не до фиксированного значения, а до того, пока будет выполняться условие. Как это сделать?

Только через while-цикл и никак иначе?

Я пробовал через for и генератор с условием, наложенным на range, но не получается:

for i in ( x for x in range(0, len(s)) if x<(len(s)-maxLen)) ):
Так тоже в процессе работы цикла количество итераций не уменьшается.

★★★★★

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

Ответ на: комментарий от anonymous

Ещё черезiter()можно

from random import randint
for i in iter(lambda:randint(1,10), 1):
   print(i)

Цикл закончится, еслиrandint()сгенерирует единицу.

Кофеечку данному анонимусу за хорошее владение не самой популярной матчастью. Если ТС не устроит этот вариант, то я даже не знаю, что ему нужно.

>>> help(iter)
Help on built-in function iter in module builtins:

iter(...)
    iter(iterable) -> iterator
    iter(callable, sentinel) -> iterator
    
    Get an iterator from an object.  In the first form, the argument must
    supply its own iterator, or be a sequence.
    In the second form, the callable is called until it returns the sentinel.
(END)
Virtuos86 ★★★★★
()
Последнее исправление: Virtuos86 (всего исправлений: 1)

А нужно, чтобы цикл бежал не до фиксированного значения, а до того, пока будет выполняться условие. Как это сделать?

Цикл for-in по учебнику это обход итератора, то есть объекта, имеющего метод next, и по факту, — одна из маленьких хитростей Python (из-за которых его ненавидят хэйтеры и обожают фанаты), — его запись представляет собой «сахарок», который можно раскрыть примерно так:

# это for-in

for i in obj:
    ...

# а это равноценный предыдущему циклу вариант
obj_iter = iter(obj): # или iter(<что-то, имеющее метод __iter__, который возвращает итератор по `obj`>)
while True:
    try:
        obj_iter.next()
        ...
    except StopIteration: # исключение генерирует `obj_iter` при очередном вызове `next`, что свидетельствует о завершении обхода итератора
        break

Собственно, к чему это? Если нужно постусловие в цикле, то а.) пишем свой obj, умеющий выдавать итератор obj_iter по себе, и б.) в методе next получаемого итератора проверяем необходимое постусловие. Или используем совет анонимуса, который как-то попроще будет в реализации.

Virtuos86 ★★★★★
()
Последнее исправление: Virtuos86 (всего исправлений: 2)
Ответ на: комментарий от Virtuos86

исключение генерирует obj_iter при очередном вызове next, что свидетельствует о завершении обхода итератора

Теперь понятно почему пайтон «тормозит» - генерация исключений там, где не нужно и все решается штатной проверкой.

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

Если очень хочеться, то вот:

def repeat_until(body, condition, *args, **kwargs):
    while True:
        if not condition(*args, **kwargs):
            body(*args, **kwargs)
        else:
            break


def body(*args, **kwargs):
    ...


def condition(*args, **kwargs):
    return ...


repeat_until(body, condition)

только, тогда и весь код надо в декларативном виде.

Upd. Тогда логично будет заменить while на рекурсию.

vvn_black ★★★★★
()
Последнее исправление: vvn_black (всего исправлений: 2)
Ответ на: комментарий от vvn_black

Во, вариант, даже должен работать, сам цикл с постусловием, универсальный:

def repeat_until(body, condition, *args, state=None, **kwargs):
    state = body(state or dict())

    if condition(state):
        return repeat_until(body, condition, state=state)

    return state

Решение с его помощью частной задачки - суммировать ввод из консоли, остановиться если введено отрицательное число:

def body(state):
    c = int(input())
    return {
        'current': c,
        'result': state.get('result', 0) + c
    }

res = repeat_until(body, lambda x: x['current'] >= 0)
print(res['result])
vvn_black ★★★★★
()
Последнее исправление: vvn_black (всего исправлений: 2)
Ответ на: комментарий от rtxtxtrx

Слушай, действительно. Я лох. Тогда ТС заблуждается насчёт «постусловности» сишного цикла for, и ему нужен либо аналог do ... while, если он хочет цикл с постусловеим, либо он всё же хочет аналог for, то есть цикл с предусловием. Оба варианта, как уже обсудили, можно реализовать через while.

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

С рекурсией проблема даже не в том, что она медленная, а в том, что она с каждой итерацией занимает всё больше памяти. Это справедливо для всех языков, в которых нет оптимизации рекурсии. На примере repeat_until(), для простого инкремента значения в state:

# ps -o rss:
    Iterations:      10,000,000
    Recursion RSS:   3,393,400 kB
    Loop RSS:        10,872 kB
    Recursion time:  10.02 s
    Loop time:       1.24 s
    
# tracemalloc 1:
    Iterations:      50,000
    Recursion mem:   8,985 kB
    Loop mem:        744 B
    
# tracemalloc 2:
    Iterations:      1,000
    Recursion mem:   180 kB
    Loop mem:        744 B

Если по времени проигрыш всего в 10 раз и я даже буду согласен подождать, в критической ситуации, то вот по памяти разница увеличивается линейно, в зависимости от количества итераций. Каждую итерацию свободная память уменьшается примерно на 180 байт.

anonymous
()

Цикл с пост-условием выполняется гарантированно хотя бы раз. for из C/C++ это цикл с предусловием, может не выполниться ни разу.

for i in ( x for x in range(0, len(s)) if x<(len(s)-maxLen)) )

Конкретно этот код, если поправить синтаксис, перебирает только начало списка, которое короче его длины на maxLen (почему это называется maxLen?). И здесь кажется нет проблемы запечь значения в range, неясно какая из переменных, кроме индекса, меняется во время итерации.

Кроме range, есть ещё enumerate и itertools.count. К примеру во время итерации размер списка постоянно меняется:

for i in itertools.count():
  ...
  if i >= len(s):
    break

Если же в теле цикла счётчик меняется, то нужен while точно. Хотя можно написать свой генератор значений и вызывать ему методы.

class Gen:
  def __init__(self):
    self._i = 0
  def __iter__(self):
    return self
  def __next__(self):
    self._i += 1
    return self._i
  def step(self, delta):
    self._i += delta

g = Gen()
for i in g:
  if ...:
    g.step(-10)
neumond
()