1

I want to write a function that takes a list of numbers and lists, which again can contain numbers and lists, and so on..., and returns the overall amount of numbers that are somewhere in the list.

Example: [1,[[[0,2],7,3],5,[1,2]],3] contains 9 numbers in it.

This is my code so far:

test=[1,[[[0,2],7,3],5,[1,2]],3]
def flatten(mylist):
    counter = 0
    for i in range(len(mylist)):
        if type(mylist[i]) == int:
            counter += 1
        if type(mylist[i]) == list:
            [item for sublist in mylist[i] for item in sublist]
            counter += 1
    return counter

I think I need to recursivley flatten the sublists. But I get the error: TypeError: 'int' object is not iterable

Maarten Fabré
  • 6,938
  • 1
  • 17
  • 36
rororo
  • 815
  • 16
  • 31
  • 1
    In the list comprehension that does not do anything (because you don't assign a name to its value) you are assuming that the elements of `mylist[i]` are lists (`for sublist in mylist[i] ...`). If `mylist[i]` holds one non-iterable element, in your specific case an integer, you will get the error. – timgeb Oct 08 '18 at 07:58

3 Answers3

6

a general approach would be to first test an item whether it is iterable. Unfortunately for this, str objects are iterable, while in most cases, they should count as one item, so should not be flattened. This method can be used for this test:

def is_iterable(item):
    """tests whether `item` is an iterable that is not a string"""
    try:
        iter(item)
        return not isinstance(item, str)
    except TypeError:
        return False

Then you can use generators and recursion to flatten the iterable:

def flatten(iterable):
    for item in iterable:
        if is_iterable(item):
            yield from flatten(item)
        else:
            yield item

list(flatten([1,[[[0,2],7,3],5,[1,2]],3] ))
[1, 0, 2, 7, 3, 5, 1, 2, 3]

Then you just need another test, the built-in sum and the fact True counts as 1 and False as 0

sum(isinstance(item, int) for item in flatten(mylist))
Maarten Fabré
  • 6,938
  • 1
  • 17
  • 36
1
def count_ints_in_list(int_list):
    def count(n):
        if isinstance(n, int):  # If it is an integer, add 1 to the count.
            return 1
        elif isinstance(n, str):  # Ignore strings.
            return 0
        elif hasattr(n, '__iter__'):  # If it is iterable, recursively call function.
            return count_ints_in_list(n)
        return 0

    if not hasattr(int_list, '__iter__'):
        return 0

    return sum(count(n) for n in int_list)  # Use a generator to sum the count of all integers.

>>> count_ints_in_list(test)
9

>>> count_ints_in_list('test')
0

# Works with lists, tuples, other non-string iterables.
>>> count_ints_in_list([1, [[(0,2), 7, 3], 5, (1,2)], 3])
9
Alexander
  • 105,104
  • 32
  • 201
  • 196
  • 1
    Note that checking for an `__iter__` method with `hasattr` is error prone. See fact 2 in [this](https://stackoverflow.com/questions/1952464/in-python-how-do-i-determine-if-an-object-is-iterable/36407550#36407550) answer. – timgeb Oct 08 '18 at 20:26
  • Noted. I am aware of those issues but felt they are well beyond the scope of the question. – Alexander Oct 08 '18 at 20:46
1

To count the elements you don't need to flatten the list. Simply

def count(lst):
    try:
        return sum(count(item) for item in lst)
    except TypeError:
        return 1

Note that it is a common approach in python to assume the object is iterable, and treat it as such, instead of checking isinstance(my_list, list) ("easier to ask for forgiveness than permission").

blue_note
  • 27,712
  • 9
  • 72
  • 90