0

I have the certain limitation in my project that I cannot use statements/keywords like while, so I'm wondering that whether there exists a function that works similar to the below code example in the standard library i.e. functools, itertools etc.?

>>> # signature: loop_while(pred, g, f, *args, **kwargs)
>>> loop_while(lambda r: r != 0,  # predicate
...            lambda v: v - 1,  # modifier function
...            lambda: 5  # initial function
... )

Where loop_while could be defined as something roughly similar to

def loop_while(pred, g, f, *args, **kwargs):
   res = f(*args, **kwargs)
   while pred(res):
       res = g(res)

Which is functionally equivalent to:

n = 5
while n != 0:
    n -= 1

Or another solution would be one that allows you to do a predicated-loop in one line e.g. [<expr> while <pred>] but using any tricks that are possible.

uspectaculum
  • 391
  • 2
  • 9
  • That's a very strange limitation. Is it for homework/assignment? And are you sure the goal isn't to get you to use recursion? – UnholySheep May 24 '18 at 17:44
  • It's a limitation inherently set when you have to write everything in one line (without semi-colon separation); it's neither homework nor an assignment, rather my own project. – uspectaculum May 24 '18 at 17:47
  • I demonstrate how to encode a basic `loop` function using python in [this answer](https://stackoverflow.com/a/46268042/633183) – and `unfold` in [this answer](https://stackoverflow.com/a/49398922/633183) – Mulan May 24 '18 at 19:04

1 Answers1

3

The itertools module (surprisingly) doesn't provide a recipe for the following function, which provides an iterable consisting of x, g(x), g(g(x)), etc:

def iterate(g, x):
    yield x
    for y in iterate(g(x), g):
        yield y

(The above is provided by the 3rd-party module toolz.itertoolz. Its definition is cleaner than mine:

def iterate(g, x):
    while True:
        yield x
        x = g(x)

)

Then your function is just a composition of iterate and itertools.takewhile:

def loopwhile(pred, g, f, *args, **kwargs):
    for x in itertools.takewhile(pred, iterate(g, f(*args, **kwargs))):
        yield x

As an example,

>>> list(loopwhile(lambda x: x < 1024, lambda x: x * 2, lambda: 1))
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512]

The Python 3 versions of the above are a little simpler:

def iterate(g, x):
    yield x
    yield from iterate(g, g(x))

def loopwhile(pred, g, f, *args, **kwargs):
    yield from takewhile(pred, iterate(g, f(*args, **kwargs)))
chepner
  • 497,756
  • 71
  • 530
  • 681