'Iterating over nested list and count specific value

I have this nested list:

d = ['good morning', 'hello', 'chair', 'python', ['music', 'flowers', 
    'facebook', 'instagram', 'snapchat', ['On my Own', 'monster', 'Words 
     dont come so easily', 'lead me right']], 'Stressed Out', 'Pauver 
     Coeur', 'Reach for Tomorrow', 'mariners song', 'Wonder sleeps here']

I need to iterate through the list so that if the character ‘m’ is in the string, then it should be added to a new list called m_list.

What I tried so far is the following:

m_list = []
for el in d:
    print(" Level1: {}".format(el))
    if type(el) is str:
        if 'm' in el:
            m_list.append(el)
    for el2 in el:
        print(" Level2: {}".format(el2))
        if type(el2) is str:
            if 'm' in el2:
                m_list.append(el2)
        for word in el2:
            print(" Level3: {}".format(word))
            if type(word) is str:
                if 'm' in word:
                   m_list.append(word)

I know that I do not set up correctly the code because inner loop will double count some elements. As example here:

print(m_list)

['good morning', 'm', 'm', 'music', 'm', 'instagram', 'm', 'On my Own', 
'monster', 'Words dont come so easily', 'lead me right', 'Reach for 
Tomorrow', 'm', 'm', 'mariners song', 'm', 'm']

I solved this using this inefficient code:

s = set(m_list)
m_list = list(s)
m_list.remove('m')
print(m_list) 
['monster', 'mariners song', 'Words dont come so easily', 'Reach for 
Tomorrow', 'On my Own', 'lead me right', 'music', 'good morning', 
'instagram']

My question how I can change for loop in order to work correctly to count character 'm' and assign to m_list?

P.S. I am not proficient user of Python. I would like to improve my skills. Would you like to suggest me smarter way to do it?



Solution 1:[1]

If you are not sure of the depth of your nesting then you need a recursive approach like this:

def find_char(char, words):
        result = []
        for word in words:
          if isinstance(word,list):
            result += find_char(char,word)
          else:
            if char in word:
              result.append(word)
        return result

>>> d = ['good morning', 'hello', 'chair', 'python', ['music', 'flowers',
'facebook', 'instagram', 'snapchat', ['On my Own', ['monster'], 'Words don`t come so easily', 'lead me right']], 'Stressed Out', 'Pauver Coeur', 'Reach for Tomorrow', 'mariners song', 'Wonder sleeps here']

>>> find_char("m",d)
['good morning', 'music', 'instagram', 'On my Own', 'monster', 'Words don`t come so easily', 'lead me right', 'Reach for Tomorrow', 'mariners song']

This will work no matter how deeply your list is nested.

Solution 2:[2]

You can create a generic custom function that calls itself if you have list within or appends the value to new list if it contains 'm' in it:

d = ['good morning', 'hello', 'chair', 'python', ['music', 'flowers', 
    'facebook', 'instagram', 'snapchat', ['On my Own', 'monster', 'Words  dont come so easily', 'lead me right']], 'Stressed Out', 'Pauver Coeur', 'Reach for Tomorrow', 'mariners song', 'Wonder sleeps here']

def find_all_values(d, m, m_list=[]):
    for x in d:
        if isinstance(x, list):
            find_all_values(x, m, m_list)
        elif m in x:
            m_list.append(x) 
    return m_list


m_list = find_all_values(d, 'm')
# ['good morning', 'music', 'instagram', 'On my Own', 'monster', 'Words  dont come so easily', 'lead me right', 'Reach for Tomorrow', 'mariners song']

m_list_count = len(m_list)
# 9

Now that you have a generic custom function, you can use this to create list that holds values containing any letter and get its count.

Solution 3:[3]

In Python 3:

def myflatten(l):
    for elem in l:
        if isinstance(elem, list):
            yield from myflatten(elem)
        else:
            if 'm' in elem:
                yield elem

list(myflatten(d))

Solution 4:[4]

You need to tell the inner for loop not to run if the element is a string. There are two common ways that apply to your case:

  1. Add an else clause to the preceding if and indent the for loop. This translates into "run this code only if the element isn't a string":

    if type(el) is str:
        if 'm' in el:
            m_list.append(el)
    else:
        for el2 in el:
            ...
    
  2. Add a continue statement if the element is a string. This translates to "don't do anything else if you have a string":

    if type(el) is str:
        if 'm' in el:
            m_list.append(el)
        continue
    for el2 in el:
    

Personally I like the first version because it is more explicit. At the same time, the second version saves you a level of indentation in the rest of your code, which can be very nice as well.

Solution 5:[5]

Try this:

m_list = []
for el in d:   
    if type(el) is str:
        if 'm' in el:
            m_list.append(el)
    else:
        for el2 in el:   
            if type(el2) is str:
                if 'm' in el2:
                    m_list.append(el2)
            else:
                for word in el2:
                    if type(word) is str:
                        if 'm' in word:
                           m_list.append(word)

print(m_list)

Solution 6:[6]

Here is a recursive solution

data = ['good morning', 'hello', 'chair', 'python', ['music', 'flowers', 'facebook', 'instagram', 'snapchat',
                                                 ['On my Own', 'monster', 'Words dont come so easily',
                                                  'lead me right']], 'Stressed Out', 'Pauver Coeur',
    'Reach for Tomorrow', 'mariners song', 'Wonder sleeps here']

m_holder = []


def m_finder(lst, m_holder):
    for word_or_list in lst:
        if isinstance(word_or_list, str):
            if 'm' in word_or_list:
                m_holder.append(word_or_list)
        else:
            m_finder(word_or_list, m_holder)


m_finder(data, m_holder)
print(m_holder)

Output:

['good morning', 'music', 'instagram', 'On my Own', 'monster', 'Words dont come so easily', 'lead me right', 'Reach for Tomorrow', 'mariners song']

Solution 7:[7]

This is a very simple solution to your answer try this:

    d = ['good morning', 'hello', 'chair', 'python', ['music', 'flowers', 
    'facebook', 'instagram', 'snapchat', ['On my Own', 'monster',
'Words dont come so easily', 'lead me right']], 'Stressed Out', 'Pauver Coeur', 
    'Reach for Tomorrow', 'mariners song', 'Wonder sleeps here']


m_list=[]

for v in d:
    if 'm' in v:
        m_list.append(v)
    if type(v) is list:
        for x in v:
            if 'm' in x:
                m_list.append(x)
            if type(x)is list:
                for y in x:
                    if 'm' in y:
                        m_list.append(y)
                    if type(y) is list:
                        for f in z:
                            if 'm' in f:
                                m_list.append(f)
                        
                       
print(m_list)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 BoarGules
Solution 2
Solution 3 Davide Albanese
Solution 4 Mad Physicist
Solution 5 Heyran.rs
Solution 6 balderman
Solution 7 Emi OB