Why not recursion? Processing recursive data structures using recursive procedures is natural and straightforward. Converting a recursive process to an iterative one doesn't have to involve cloning the input, creating a stack
, or other intermediate values. Your brain can be free from such crippling complexities -
def first (a = []):
return a[0]
def rest (a = []):
return a[1:]
def myiter (a = []):
# base: empty a
if not a:
return []
# inductive: non-empty a, first elem is list
elif isinstance(first(a), list):
return [ myiter(first(a)) ] + myiter(rest(a))
# inductive: non-empty a, first elem is non-list
else:
return [ str(first(a)) ] + myiter(rest(a))
print(myiter([1, [2, [3, 4], 5]]))
Of course it makes sense to make str
a parameter of the function, f
-
def myiter (f, a = []):
# base: empty a
if not a:
return []
# inductive: non-empty a, first elem is list
elif isinstance(first(a), list):
return [ myiter(f, first(a)) ] + myiter(f, rest(a))
# inductive: non-empty a, first elem is non-list
else:
return [ f(first(a)) ] + myiter(f, rest(a))
Now you can deep map str
like you want to -
print(myiter(str, [1, [2, [3, 4], 5]]))
# ['1', ['2', ['3', '4'], '5']]
Or use any function of your choosing -
def square (x):
return x * x
print(myiter(square, [1, [2, [3, 4], 5]]))
# [1, [4, [9, 16], 25]]
Are you trying to avoid recursion because of a stack limit? If you make it tail-recursive -
def identity (x):
return x
def myiter (f, init = []):
def run (a = init, then = identity):
if not a:
return \
then([])
# inductive: non-empty a, first elem is list
elif isinstance(first(a), list):
return \
recur(first(a), lambda l: \
recur(rest(a), lambda r: \
then([ l ] + r)))
# inductive: non-empty a, first elem is non-list
else:
return \
recur(rest(a), lambda r: \
then([ f(first(a)) ] + r))
# loop inner function
return loop (run)
Then implement a generic loop
which converts the recursive call stack to an iterative sequence -
def recur (*values):
return (recur, values)
def loop (f):
acc = f ()
while type(acc) is tuple and acc[0] is recur:
acc = f(*acc[1])
return acc
The output is the same, but now myiter
can accept an array of any nesting limit. Recursion without restrictions; what a beautiful thing -
print(myiter(str, [1, [2, [3, 4], 5]]))
# ['1', ['2', ['3', '4'], '5']]
print(myiter(square, [1, [2, [3, 4], 5]]))
# [1, [4, [9, 16], 25]]
View this program and verify the results in your own browser using repl.it.
Why can't you use recursion? - Jab
@Jab, Three reasons: First maximum recursion limit is often hit on one of my app, second performance concerns althought map might be head to head with iterative method, thirdly just for training and studying these two different styles of coding. – MarkokraM
So you haven't hit a recursion limit but you're worried that your program will? It's better to understand actual limitations instead of writing programs around ghosts. In a simplified implementation using generators, notice that recursion only happens when a level of nesting is encountered. Even if you left this implementation as-is, this can support lists of any length and nesting levels up to the stack limit, where the default is probably around 1,000. This means the only data input that would blow your stack is one that is nested 1,000 times or more. It's probably safe to leave this program be until an actual limitation is reached.
def square (x):
return x * x
def myiter (f, init = []):
def gen (a):
for x in a:
if isinstance(x, list):
yield list(gen(x)) # recursion
else:
yield f(x)
return list(gen(init))
print(myiter(str, [1, [2, [3, 4], 5]]))
# ['1', ['2', ['3', '4'], '5']]
print(myiter(square, [1, [2, [3, 4], 5]]))
# [1, [4, [9, 16], 25]]