2

I am trying to get the length of a list, but that list can contain other lists that are deeply nested (e.g. [[[[[[[]]]]]]]).

But you have to get any non list elements in the count as well: [1, 2, [3, 4]] would output 5 (1, 2, 3, 4 and a list). ["a", "b", ["c", "d", ["e"]]] would output 7.

It first comes to mind to use recursion to solve this problem. I wrote this:

def deep_count(arr, count=0):
    if any(type(i) == list for i in arr):
        for i in arr:
            if type(i) == list:
                count += len(arr)
            else:
                count += 1
        return deep_count(arr[1:], count)
    return count

It's outputting 9 instead of 7. And if it's just nested lists like [[[]]] it only outputs 1.

taylorSeries
  • 505
  • 2
  • 6
  • 18

6 Answers6

4

There doesn't seem to be a need for supplying an initial value, you can just use arr as the only parameter, then just get the count and if the current element is a list then also add that list's count.

def f(arr):
    count = len(arr)
    for element in arr:
        if isinstance(element, list):
            count += f(element)
    return count

>>> f([1, 2, [3, 4]])
5
>>> f(["a", "b", ["c", "d", ["e"]]])
7
Jab
  • 26,853
  • 21
  • 75
  • 114
4

Recursively adding all the list lengths (Try it online!):

def f(xs):
    return len(xs) + sum(f(x) for x in xs
                         if isinstance(x, list))

A variation (Try it online!):

def f(xs):
    return isinstance(xs, list) and len(xs) + sum(map(f, xs))
Kelly Bundy
  • 23,480
  • 7
  • 29
  • 65
  • I like this solution. Simple, and to the point. – Jhanzaib Humayun May 20 '22 at 16:49
  • 1
    Indeed a very pretty solution, although less efficient than the standard loop approaches in my answer. At least from my tests. [Try it online!](https://tio.run/##nZHBioMwEIbveYohp0g9rHpZCn2SIkvUCQ2YKMkI3ad3k5hWC4XCegiTmX/m/ybOv3SbbPM9u3VVbjJA2qAm0GaeHOUbYwMqUJWQzhVnBuHzS/fTT4sluMBXyuhnpCYHugQc0WAQaAtoF4NOEsYJJVR5SGpToL22nqTtUeSeEkbt6aB6tTxdIkzWFknkkBZnA8Rp12Xs@oD9ihxBD5RBtjs@nap/oO6Y9VvMDS8mNsRG3H0e8VhkN7r77AHSDjCijeK0pxFGzkI1JYRMwcK@kmTY7solL4F34bjyPoZDCpG3bctmpy2J7c@KUZpukOf4nrG5CGPe1@sP9eZRX9c/ "Python 3.8 (pre-release) – Try It Online") – Jab May 20 '22 at 17:07
  • @Jab Hmm, you' only measured my second solution... – Kelly Bundy May 20 '22 at 17:09
  • 1
    @Jab Although yes, both are slower. Weren't intended to be fast. And my original suggestion [`f2b` wins](https://tio.run/##nZLdaoQwEIXvfYrBq8h6UX8uysI@ySIl0QkNmChJBPv0Nonpqu1SYXMh48w5cz5Cxi/7OajqfdTLwvUgwQqJwoKQ46Bt/EuSDjnwglCts2sC7piJfbTDpCzc4C10xKPigwaRA/Yo0QmEAlSTRE0t@g05FHFJsHEQRihjqWqRRE8OvTB2pzpGXm4eJmqzINJoJ60cxGXTRexyh31E9qA7SifbEh9JxQuoG2b5FHPFg8jHngD2qEL3lPMlKPYP1QpVkdlEf5x6ItcLFyyJF2QBbPZIszlEHc6RcI5sIXnNqv9k7fUmGoCq7heEpCPhde7S3ToHTi11V3dPaZpDytznnra@7EKJadM0yaiFsmR916SnknX06l@TN2duzfN5eTZnJ4LqZF7/zJflGw) :-) – Kelly Bundy May 20 '22 at 17:14
2

When talking recursion, always think of the base case (which case would not make a recursion). In your problem, it would be when the list is empty, then it would return a count of 0.

So you could start implementing it like so:

def f(arr):
    if arr == []:
        return 0

For the rest of the implementation, you have two approaches:

  • Looping over the list and add f(sublist) to the count when it is effectively a sublist, and add 1 when it isn't
  • Go full recursion and always call the f(x) function, no matter if it is a sublist or not, and always add the result to the count. In this case, you have a new base case where f(not_a_list) would return 1

I think this should unstuck you

Note: I just read that recursion is not required, you came up with it. I think this is a good approach for this kind of problem

jkoestinger
  • 980
  • 6
  • 8
1

My old answer takes the result of the last example of OP as the expected output. If it is not considered, the answer is very simple:

>>> def count_deep_list(lst):
...     return sum(count_deep_list(val) for val in lst if isinstance(val, list)) + len(lst)
...
>>> count_deep_list([1, 2, [3, 4]])
5
>>> count_deep_list(["a", "b", ["c", "d", ["e"]]])
7
>>> count_deep_list([[[]]])
2

Old Answer:

It seems that you need a special judgment on the internal empty list and the incoming list itself. I think it needs to be implemented with the help of closure:

>>> def count_deep_list(lst):
...     def count(_lst):
...         if isinstance(_lst, list):
...             if not _lst:
...                 return 0
...             else:
...                 return sum(map(count, _lst)) + 1
...         else:
...             return 1
...
...     return count(lst) - 1 if lst else 0
...
>>> count_deep_list([1, 2, [3, 4]])
5
>>> count_deep_list(["a", "b", ["c", "d", ["e"]]])
7
>>> count_deep_list([[[]]])
1

Simpler implementation:

>>> def count_deep_list(lst):
...     def count(_lst):
...         return sum(count(val) if val else -1 for val in _lst if isinstance(val, list)) + len(_lst)

...     return count(lst) if lst else 0
...
>>> count_deep_list([1, 2, [3, 4]])
5
>>> count_deep_list(["a", "b", ["c", "d", ["e"]]])
7
>>> count_deep_list([[[]]])
1
Mechanic Pig
  • 6,756
  • 3
  • 10
  • 31
1

A twoline answer would be:

def lenrecursive(seq):
    return len(seq) + sum(lenrecursive(s) for s in seq if isinstance(s,Sized) and not isinstance(s, str))

However this will not take into account that other things may have a len

A more complete handling using EAPF approach is:

def lenrecursive(seq):
    """
    Returns total summed length of all elements recursively.
    If no elements support len then return will be 0, no TypeError will be raised
    """
    def _len(x):
        try:
            return len(x)
        except TypeError: #no len
            return 0
    
    try:
        return _len(seq) + sum(lenrecursive(s) for s in seq if not isinstance(s, str))
    except TypeError: #not iterable
        return _len(seq)

Notes:

  1. Why not isinstance(s, str)? - Strings will lead to infinite recursion as a one-character string is a sequence so lenrecursive(a) would return 1 + lenrecursive(a) which is 1 + 1 + lenrecursive(a) which is 1 + 1 + 1 + ...
  2. Why EAFP using try: except: rather than LBYL checking with isinstance? - because there are so many things out there that support len and you don't know whether the next time you call this you've created a custom class which has a __len__ but doesn't subclass Sequence, Mapping, or something else that inherits from collections.Sized
-1
class Counter:
def __init__(self):
    self.counter = 0

def count_elements(self, l):
    for el in l:
        if type(el) == list:
            self.counter += 1
            self.count_elements(el)
        else:
            self.counter += 1

l = ["a", "b", ["c", "d", ["e"]]]

c = Counter()
c.count_elements(l)
print(c.counter)
John Brown
  • 111
  • 1
  • 9