1

i'm trying to generate an endless stream of results given a function f and an initial value x so first call should give the initial value, second call should give f(x), third call is f(x2) while x2 is the previous result of f(x) and so on..

what i have come up with:

def generate(f, x): 
   return itertools.repeat(lambda x: f(x))

which does not seem to work. any ideas? (i cant use yield in my code). also i cant use more than 1 line of code for this problem. any help would be appreciated.

also note that in a previous ex. i was asked to use the yield. with no problems:

while True:
    yield x
    x = f(x)

this works fine. but now.. no clue how to do it without

David
  • 315
  • 3
  • 14
  • 5
    What's with those constraints? Seems fairly artificial? – Jon Clements Nov 27 '13 at 17:13
  • 6
    Wow ... whoever gave you this assignment should re-think it... As far as I see it, this is an exercise in teaching people to do a task *the wrong way* – mgilson Nov 27 '13 at 17:13
  • @NPE And that too in one line. :( – thefourtheye Nov 27 '13 at 17:13
  • yep, i had a previous assignment doing it with yield. ill edit my post – David Nov 27 '13 at 17:16
  • hmm well it works fine for the yield. – David Nov 27 '13 at 17:19
  • @mgilson I think that while loop is an alternate body to the same `generate` function he has defined above, just without the function declaration enclosing it. Then it would work fine. – Silas Ray Nov 27 '13 at 17:23
  • @SilasRay -- I missed that OP said first call should give the initial value. I thought the first call should give `f(x)`. – mgilson Nov 27 '13 at 17:25
  • Have a look here may be u find something useful.. http://stackoverflow.com/questions/5737196/an-expression-for-an-infinite-generator.. – xor Nov 27 '13 at 17:26
  • Also if you are using `itertools.repeat()` then it uses `yield` internally... so one way or the other you will have to use `yield`... look here http://docs.python.org/2/library/itertools.html#itertools.repeat – xor Nov 27 '13 at 17:29

3 Answers3

4

In Python 3.3, you can use itertools.accumulate:

import itertools

def generate(f, x):
  return itertools.accumulate(itertools.repeat(x), lambda v,_:f(v))

for i, val in enumerate(generate(lambda x: 2*x, 3)):
  print(val)
  if i == 10:
    break
NPE
  • 486,780
  • 108
  • 951
  • 1,012
4

I think this works:

import itertools as it
def g(f, x):
    return it.chain([x],(setattr(g, 'x', f(getattr(g, 'x', x))) or getattr(g, 'x') for _ in it.count()))

def f(x):
    return x + 1

gen = g(f, 1)
print next(gen)
print next(gen)
print next(gen)
print next(gen)

Of course, it relys on some sketchy behavior where I actually add an attribute to the function itself to keep the state. Basically, this function will only work the first time you call it. After that, all bets are off.

If we want to relax that restriction, we can use a temporary namespace. The problem is that to get a temporary namespace we need a unique class instance (or class, but an instance is cleaner and only requires 1 extra set of parenthesis). To make that happen in one line, we need to create a new function inline and use that as a default argument:

import itertools as it
def g(f, x):
    return (lambda f, x, ns=type('foo', (object,), {})(): \
        it.chain([x], 
                 (setattr(ns, 'x', f(getattr(ns, 'x', x))) or getattr(ns, 'x') 
                          for _ in it.count()))
            )(f, x)

def f(x):
    return x + 1

gen = g(f, 1)
print next(gen) == 1
print next(gen) == 2
print next(gen) == 3
print next(gen) == 4

print "first worked?"
gen2 = g(f, 2)
print next(gen2) == 2
print next(gen2) == 3
print next(gen2) == 4

I've broken it into a few lines, for readability, but it's a 1-liner at heart.

A version without any imports

(and the most robust one yet I believe).

def g(f, x):
    return iter(lambda f=f, x=x, ns=type('foo', (object,), {'x':x}): ((getattr(ns, 'x'),setattr(ns, 'x', f(getattr(ns, 'x'))))[0]), object())

One trick here is the same as before. We create a lambda function with a mutable default argument to keep the state. Inside the function, we build a tuple. The first item is what we actually want, the second item is the return value of the setattr function which is used to update the state. In order to get rid of the itertools.chain, we set the initial value on the namespace to the value of x so the class is already initialzed to have the starting state. The second trick is that we use the two argument form of iter to get rid of it.count() which was only used to create an infinite iterable before. iter keeps calling the function you give it as the first argument until the return value is equal to the second argument. However, since my second argument is an instance of object, nothing returned from our function will ever be equal to it so we've effectively created an infinite iterable without itertools or yield! Come to think of it, I believe this last version is the most robust too. Previous versions had a bug where they relied on the truthfulness of the return value of f. I think they might have failed if f returned 0. This last version fixes that bug.

mgilson
  • 300,191
  • 65
  • 633
  • 696
  • I was thinking of the mutable default argument state hack. – DSM Nov 27 '13 at 17:40
  • @DSM -- Yeah, that would work too. I actually got rid of that statefulness problem by returning the return value of a lambda function with mutable state :) – mgilson Nov 27 '13 at 17:43
  • FWIW, I think this takes it for the most obfuscated python code I've ever written ... Thanks professor. – mgilson Nov 27 '13 at 17:52
  • Your new code is.. a bunch of characters one could type. |^) I may "borrow" this approach for the next round of one-liner golf! – DSM Nov 27 '13 at 17:54
  • I reckon that `chain` and `count` can be gotten rid of :) – Jon Clements Nov 27 '13 at 17:56
  • @JonClements -- If you can figure out how, I'm open to suggestions. While trying, my brain started to hurt, so I stopped trying. – mgilson Nov 27 '13 at 17:57
  • @mgilson I've got an idea... but my brain is protesting... I'll let you know if I manage it :) – Jon Clements Nov 27 '13 at 17:58
  • @JonClements I think you could get rid of `count` using the 2 argument form of `iter` ... – mgilson Nov 27 '13 at 18:03
  • @mgilson yeah... and I was thinking of the two form version of `next` might be able to get rid of the chain... - Something like: `next([lambda x: x], lambda L: f(L))` sort of lines... So the first iteration just has an identity function instead of the actual function – Jon Clements Nov 27 '13 at 18:06
  • 1
    @JonClements -- I got it! :). No imports :). – mgilson Nov 27 '13 at 18:15
  • @DSM -- Added an importless version for the next time that's a requirement in your code-golfing. – mgilson Nov 27 '13 at 18:17
  • Now I have to pretend that I've always known about the two-argument version of `iter` and I haven't used it in the past for some artistic reason. I have mixed feelings about this development. – DSM Nov 27 '13 at 18:21
  • 3
    To borrow from Bob (I believe) - it should have a disclaimer like: I can see it can you see ̲͚̖͔̙î̩́t̲͎̩̱͔́̋̀ it is beautiful t​he final snuffing of the lie​s of Man ALL IS LOŚ͖̩͇̗̪̏̈́T ALL I​S LOST the pon̷y he comes he c̶̮omes he comes the ich​or permeates all MY FACE MY FACE ᵒh god no NO NOO̼O​O NΘ stop the an​*̶͑̾̾​̅ͫ͏̙̤g͇̫͛͆̾ͫ̑͆l͖͉̗̩̳̟̍ͫͥͨe̠̅s ͎a̧͈͖r̽̾̈́͒͑e n​ot rè̑ͧ̌aͨl̘̝̙̃ͤ͂̾̆ ZA̡͊͠͝LGΌ ISͮ̂҉̯͈͕̹̘̱ TO͇̹̺ͅƝ̴ȳ̳ TH̘Ë͖́̉ ͠P̯͍̭O̚​N̐Y̡ H̸̡̪̯ͨ͊̽̅̾̎Ȩ̬̩̾͛ͪ̈́̀́͘ ̶̧̨̱̹̭̯ͧ̾ͬC̷̙̲̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̲̖͊̒ͪͩͬ̚̚͜Ȇ̴̟̟͙̞ͩ͌͝S̨̥̫͎̭ͯ̿̔̀ͅ – Jon Clements Nov 27 '13 at 18:21
  • @DSM -- Don't feel bad. I don't think I've ever used the 2 argument form of `iter` in a single piece of code that I've actually used for something other than a SO answer where there was some silly constraint. – mgilson Nov 27 '13 at 18:27
0

I'm guessing this is some sort of homework or assignment? As such, I'd say you should take a look at generator expressions. Though I agree with the other commenters that this seems an exercise of dubious value...

Silas Ray
  • 25,682
  • 5
  • 48
  • 63
  • yea i was thinking itertools also, but the only one that seems to do the trick is imap? – David Nov 27 '13 at 17:23
  • I misread the question slightly and was thinking about `count`. That won't work for what you want, though you may be able to shoehorn something in to something from `itertools`... – Silas Ray Nov 27 '13 at 17:25