-1

My actual problem is much larger, but the gist of it is something like:

def rec_func(it, newit, idx=0, seen_5=False, even_seen=0):
    num = next(it, None)
    if num is None:
       return newit
    elif num == 5: seen_5 = True
    elif num&1==0: even_seen +=1
    else: num*=20
    newit.append(num)
    print '[{}] seen_5 = {}, even_seen = {}'.format(idx, seen_5, even_seen)
    return rec_func(it, newit, idx+1, seen_5, even_seen)

Run with (http://ideone.com/4kBbhn):

a = []
rec_func(iter(xrange(10)), a)
print 'a =', a

But there are some serious problems with this design, recursion isn't that efficient in Python and a list is being created and passed through just for its result (which isn't a generator). Trying to get this:

a = tuple(itertools.<something>(func, iter(xrange(10)))
print a # same answer as before

How do I? - All I can think of is:

def func(num, state):
    if num == 5: state['seen_5'] = True
    elif num&1==0: state['even_seen'] +=1
    else: num*=20
    print '[?] seen_5 = {}, even_seen = {}'.format(state['seen_5'],
                                                   state['even_seen'])
    return num

_state = {'seen_5': False, 'even_seen': 0}
print tuple(imap(partial(func, state=_state), xrange(10)))

Which works, but seems kind of horrible... is there a functional solution?

A T
  • 13,008
  • 21
  • 97
  • 158
  • 1
    If you want to retain state, why not write a class, create an instance, and then map a method over the iterable? Then the method can store whatever state it needs on the instance. Trying to do this just with a function seems needlessly restrictive. – BrenBarn Jan 20 '17 at 07:30
  • @BrenBarn There must be reason, since OP tagged `purely-functional`. – Right leg Jan 20 '17 at 07:32
  • I've edited the example passing in an object that holds its own memory (so passes by reference in Python). It's ugly and I would prefer a functional solution, do you know of one? - Maybe using accumulators? – A T Jan 20 '17 at 07:35
  • I think that you should choose between "functional" and "efficient". If you absolutely want to do this in a functional way, since Python is not aimed at powerful functional programming, then you should accept that the solution you want is not an efficient one. On the other hand, using a `while`, beside being probably the best solution, seems acceptable, since you make use of `if` and `else` conditions, which is not purely functional. – Right leg Jan 20 '17 at 07:41
  • 2
    Can you provide a simpler example of what you're trying to do, or at least explain what your existing example is supposed to achieve? You example returns a tuple of stuff but you don't explain its purpose at all. – BrenBarn Jan 20 '17 at 07:41
  • 1
    Would the functional approach not be to make the values you want to track part of the return value so you can use them in another call: `return newit, seen_5, even_seen` – user2390182 Jan 20 '17 at 07:43
  • @schwobaseggl how would that differ from the recursive solution? - Yeah I was thinking maybe a **fold**. BrenBarn I often find myself in this pattern where *state* is required, so I've written up the smallest test-case I could think of. – A T Jan 20 '17 at 07:45
  • @AT: Great. Can you ***explain*** what your test case is supposed to accomplish, instead of just showing the tuple that it returns? And also explain why you want to do it in a "pure functional" way, even though that does not appear to be the best way? – BrenBarn Jan 20 '17 at 07:46
  • @AT What if the functional approach **requires** a recursion? – Right leg Jan 20 '17 at 07:46
  • 1
    @Rightleg: Yeah that's fine, but I'm thinking that this all should be handled with function calls rather than explicit recursion. So yeah, something like `reduce`/`foldl` but maybe there's another approach to this? - @BrenBarn: The current problem I'm solving is a combined parser/emitter that runs in a single iteration. Trivial to solve in a `for` loop, but I want to know if there's a clean functional solution. – A T Jan 20 '17 at 07:50

1 Answers1

1

One way to get rid of the recursion and the intermediate lists is using a generator:

from __future__ import print_function


def gen(it):
    idx = 0
    seen_5 = False
    even_seen = 0
    for num in it:
        if num == 5: 
            seen_5 = True
        elif num&1==0: 
            even_seen +=1
        else: 
            num *= 20            
        print('[{}] seen_5 = {}, even_seen = {}'.format(idx, seen_5, even_seen))
        idx += 1
        yield num

print('a =', list(gen(iter(range(10)))))

Output:

[0] seen_5 = False, even_seen = 1
[1] seen_5 = False, even_seen = 1
[2] seen_5 = False, even_seen = 2
[3] seen_5 = False, even_seen = 2
[4] seen_5 = False, even_seen = 3
[5] seen_5 = True, even_seen = 3
[6] seen_5 = True, even_seen = 4
[7] seen_5 = True, even_seen = 4
[8] seen_5 = True, even_seen = 5
[9] seen_5 = True, even_seen = 5
a = [0, 20, 2, 60, 4, 5, 6, 140, 8, 180]
A T
  • 13,008
  • 21
  • 97
  • 158
Mike Müller
  • 82,630
  • 20
  • 166
  • 161
  • True, that is a clean solution. Hmm, I was trying with `yield` earlier today, ah I know what I did wrong! - I put the `yield` inside a function that I called to loop over `it`. Obviously this solution will work, so thanks. - Will lay off accepting for now, want to see what others come up with. - +1 – A T Jan 20 '17 at 07:53
  • That's a good solution, but it's pretty far from being purely functional. – Right leg Jan 20 '17 at 07:55
  • Yeah @Rightleg - the [best] purely functional solution will get accepted as the answer – A T Jan 20 '17 at 07:56