Just Fixing the Issue:
You have to handle the case of an empty list in your recursion. A minimal (and admittedly somewhat hacky) change to your code would look something like this:
import sys
def find(abc):
#base case
if len(abc) == 2:
if isinstance(abc[0], list) and isinstance(abc[1], list):
re = find(abc[0] + abc[1:])
elif isinstance(abc[1], list):
re = find(abc[:1] + abc[1])
elif isinstance(abc[0], list):
re = find(abc[0] + abc[1:])
# ^^^ fixed typo (ifs could be simplified by dropping first if)
else:
if abc[0] > abc[1]:
re = (abc[0], abc[1])
else:
re = (abc[1], abc[0])
# recursive step
else:
# CHANGE HERE
if len(abc) == 0: # @HACK: handle empty list
return (sys.maxsize, sys.maxsize)
# CHANGE ENDS
if isinstance(abc[0], list):
re = find(abc[0] + abc[1:])
# if this element is integer
else:
current = abc[0]
second, first = find(abc[1:])
if (second < current):
re = (second, first)
elif (current > first) and (second >= current):
re = (current, first)
else:
re = (first, current)
return re # @TODO: possibly filter out maxsize in final result
This is far from perfect (e.g. yielding maxsize if there are not enough values and maybe having additional bugs).
Refactoring Your Code:
Therefore, I would refactor your code in two ways. Firstly, I would separate out flattening and searching (i.e. flatten first and then search the flattened list):
def flatten(li):
for el in li:
try: # if strings can be in list, would have to check here
for sub in flatten(el):
yield sub
except TypeError:
yield el
def find(abc):
abc = list(flatten(abc))
def _smallest2(abc):
# basically your implementation for finding the smallest two values
if len(abc) <= 2:
return tuple(sorted(abc, reverse=True))
current = abc[0]
second, first = _smallest2(abc[1:])
if (second < current):
re = (second, first)
elif (current > first) and (second >= current):
re = (current, first)
else:
re = (first, current)
return re
return _smallest2(abc)
And secondly, I would use heapq.nsmallest
instead of your implementation for searching:
import heapq
def flatten(li):
for el in li:
try: # if strings can be in list, would have to check here
for sub in flatten(el):
yield sub
except TypeError:
yield el
def find(li):
return tuple(heapq.nsmallest(2, flatten(li))[::-1])
If you are ok with a slightly different return value, feel free to drop the tuple
and [::-1]
.
Alternative Implementation:
While I'd prefer the refactored code above for various reasons (e.g. robustness, expressiveness), here is an alternative implementation which arguably is more in line with your initial question. The main idea behind this implementation is to only check whether the first element is a list? If yes, flatten; if no, recursively go down the tail of the list:
def find(abc):
try: # first element is list
return find(abc[0] + abc[1:]) # -> flatten
except: # first element is value
if len(abc) <= 1: return abc # -> either end recursion
return sorted(abc[:1] + find(abc[1:]), reverse=True)[-2:] # -> or recurse over tail
Note that the return type is slightly different (list instead of tuple). And you could replace sorted
with heapq.nsmallest
(which arguably is more efficient for small n
).