How to move strings in a list with a defined shared substring to a new list?

Viewed 63

I'm trying to create two lists, where bracketed substrings in the second list will never occur in the first list.

Given a starting list of strings and an empty list:

word_list = ['{a==meliorate}>ed>','{a==meliorate}>s>','{a==meliorate}','{anew}','{annex}>ing>','{anvil}>ed>','{anvil}>ing>','{anvil}','<un<{ban}>ed>','<re<{write}']

new_list=[]

I would like to be able to split word_list, so that half of the words are popped to new_list. However, for a given bracketed {substring}, if it is found in word_list it should not be found in new_list and vice versa.

So we would have:

word_list = ['{anew}','{anvil}>ed>','{anvil}>ing>','<re<{apply}','<un<{ban}>ed>']

new_list=['{a==meliorate}>ed>','{a==meliorate}>s>','{a==meliorate}','<re<{write}','{annex}>ing>']

My attempt so far:

regex = re.compile('.*({[a-z]+}).*')
matches=[]

for element in word_list:
    m = re.search(regex, element)
    if m:
        root = m.group(1)
        matches.append(root)

while counter < len(word_list)/2:
    randroot = random.choice(matches) #select a random {root}
    indices = [i for i, e in enumerate(matches) if e == randroot] #get indices of all words with given root
    for index in indices: #for each index of root-aligned words, appends corresponding word 
        new_list = word_list.pop(index)

However, my output seems to be random, with strings containing the bracketed elements appearing on both lists. Any help is much appreciated!

2 Answers

So, for starters your regex will not actually match all of your bracketed words as displayed. .*({[a-z]+}).* won't match: {a==meliorate} I would almost assume the equals signs are typos, but if not - consider swapping the {[a-z]+} to something like {.+}

That aside, your generator also has an issue. i for i, e in enumerate(matches) if e == randroot will not actually check if the word matches the root, because you're seeing if the word is and only is, the root. That is to say, e = {write} thus e != re{write}. You should instead be doing a regex check on the words you pull to see if they contain the root, rather than are the root.

The other answer has already covered that the regex will not match any strings with "=" in them and that your comparison will not result in your output, but the matches instead..

perhaps the biggest problem is that when you pop an element from a list you change it's length, and therefore all the indexes of the elements inside. This is why your output is more random than you expected. If you were to pop an early element and then try to pop the last element you'd also encounter an IndexError.

I've tweaked the code to not rely on indexes. Which is probably the best way to deal with iterables that are changing length.

#!/usr/bin/env python3
import re
import random

word_list = ['{a==meliorate}>ed>','{a==meliorate}>s>','{a==meliorate}','{anew}','{annex}>ing>','{anvil}>ed>','{anvil}>ing>','{anvil}','<un<{ban}>ed>','<re<{write}']

new_list=[]

regex = re.compile(r".*({[a-z=]+}).*")
matches=[]

for element in word_list:
        m = re.search(regex, element)
        if m:
                root = m.group(1)
                matches.append(root)

target = len(word_list) / 2
while len(new_list) < target:
        randroot = random.choice(matches) # select a random {root}
        found_words = [w for w in word_list if randroot in w] # get all words with given root in them

        if len(found_words) > target - len(new_list):
                continue

        new_list.extend(found_words)
        word_list = [w for w in word_list if w not in new_list] # remove all the words we just added

print(word_list)
print(new_list)

Explanation of changes: I simply added "=" into your regex to catch "a==meliorate". I set the target as a variable, because the length of word_list is going to change.

I now just check if the match is in the strings from word_list instead of looking for an exact match.. this is not a completely error-proof approach, but looking at your input data I think it is safe to use here.

The if check helps us to make sure we end up with even lengths for each list. For instance we won't add "a==meliorate", which occurs 3 times.. if we only have 2 more slots to reach the target. Beware though, if the list can't be evenly split this will cause an infinite loop.

We add the found words to new_list with extend. And now we rebuild word_list, excluding any values found in new_list..

Result:

['{a==meliorate}>ed>', '{a==meliorate}>s>', '{a==meliorate}', '{anew}', '<un<{ban}>ed>']
['{annex}>ing>', '{anvil}>ed>', '{anvil}>ing>', '{anvil}', '<re<{write}']
Related