Remove tuples from a list if their first items are equal to any of the given strings

Viewed 944

I have a list as below:

device = [('nvme2n1',), 
          ('nvme1n1', '/local'), 
          ('nvme0n1',), 
          ('nvme0n1p1', '/'),
          ('nvme0n1p128',), 
          ('nvme3n1',)]

I want to delete few tuples from this list containing nvme1n1 or nvme0n1p1 or nvme0n1p128 or nvme0n1.

so the final list will have

final_device = [('nvme2n1',),('nvme3n1',)]

tried as below but didn't work & got error "AttributeError: 'tuple' object has no attribute 'startswith'"

for word in devices[:]: 
    if word.startswith("nvme0n1","nvme0n1p1","nvme0n1p128"): 
        devices.remove(word)

Can anyone help with this?

5 Answers
devices = [('nvme2n1',), ('nvme1n1', '/local'),
           ('nvme0n1',), ('nvme0n1p1', '/'), 
           ('nvme0n1p128',), ('nvme3n1',)]
devices = [device for device in devices if device[0] not in 
           (("nvme0n1", "nvme0n1p1", "nvme0n1p128", "nvme0n1"))]
print(devices)

output

[('nvme2n1',), ('nvme1n1', '/local'), ('nvme3n1',)]

@JamesTollefson in their answer address the particular problem in your code and how to remedy it. This is just different and in my opinion better/cleaner way to achieve what you want.

The words you are iterating through in the devices list are tuples. You need to access that part of that tuple you are interested in as follows:

for word in devices[:]: 
    if word[0] in ["nvme0n1","nvme0n1p1","nvme0n1p128"]: 
        devices.remove(word)

You can even use a list comprehension to make it simpler. As James mentioned in his answer, you need to do word[0] as word is a tuple and not a string.

startswith can take a tuple to check with.

[dev for dev in devices if not dev[0].startswith(("nvme0n1","nvme0n1p1","nvme0n1p128"))]

But if you're looking for exact matches, you can do,

[dev for dev in devices if dev[0] not in ("nvme0n1","nvme0n1p1","nvme0n1p128")]

@JamesTollefson's answer is nice and gets to the heart of the matter.

As a side note, just wanted to add that when dealing with lists and dicts, you can make your code more concise and elegant using list comprehensions:

devices = [d in devices if d[0] not in {"nvme0n1", "nvme0n1p1", "nvme0n1p128", "nvme0n1"}]

The question is a bit vague on exactly what criteria is to be measured.

E.g. does it have to be an exact match , contain or start with.

Below is a comprehensive solution I made to come up with four cases and solutions.

There is also the additional element of matching anything in a tuple if the index is unknown.

If you find any issues with the code/have suggestions I'll happily edit.

There's also future proofing so using the list of items to be removed inline and not setting it to a variable should be avoided. This solution is therefore more general.

Using the below with: final_device = remove_from_tuple_list(device,remove_list,0,match_type = 'contains')

Solves the OP's specific problem.

The code is fully commented with explantions.

device = [('nvme2n1',), 
          ('nvme1n1', '/local'), 
          ('nvme0n1',),
          ('nvme0n1p1', '/'),
          ('nvme0n1p128',),
          ('nvme3n1',)]

remove_list = ['nvme1n1', 'nvme0n1p1' , 'nvme0n1p128', 'nvme0n1']

def analyze_item_criteria(item : str ,criteria : list,criteria_type : str):
    '''
    Check whether an item string or tuple is contained in a list of items

    Args:
        item : the item to be evaluated
        criteria : list of items to be checked against
        criteria_type: the type of criteria

    Returns:
        Boolean True/False if evaluated
        None if not evaluated
    
    Raises:
        'Invalid criteria_type specified' : if the item is not a valid criteria type

    '''
    ##Check that type is valid
    assert criteria_type in ['contains',
                          'exact',
                          'starts_with',
                          'ends_with',
                          ],'Invalid criteria_type specified'

    if criteria_type == 'exact':
        if item in criteria:
            return True
        else:
            return False
    ##Starts with/ends with also takes a tupe (.startswith((args,))) this implementation
    ##Isn't really different from what happens under the hood
    elif criteria_type == 'starts_with':
        if any([item.startswith(x) for x in criteria] ):
            return True
        else:
            return False
    elif criteria_type == 'ends_with':
        if any([item.endswith(x) for x in criteria] ):
            return True
        else:
            return False
    elif criteria_type == 'contains':
        if any([x in item for x in criteria]):
            return True
        else:
            return False
#Specifying no index does not really work with well exact but the other three options are still valid
def remove_from_tuple_list(tuple_list : list, remove_list :list, tuple_index : int = None, match_type : str = 'contains'):
    '''
    Function to remove items from a list of tuples containing specific words.
    The function can remove items at a specified index or any position if not specified

    Args:
        tuple_list : The list of tuples from which items are to be removed (items in tuple have to be str/int/float , objects will
                      have unexpected behaviour)
        remove_list : list of strings/items to be removed from the tuple_list
        tuple_index : if an integer, checks for criteria at the specific position otherwise the whole tuple which is inefficient
                      Set to none if you want to analyze a list of strings for example as well
        match_type : Do we want to match the word exactly or do we look for it containing the word
                      There are a few cases to consider here.
                      - contains
                      - exact
                      - starts_with
                      - ends_with
    Returns:
        list of items not in remove_list

    Raises:
        'Invalid match_type specified' : if the item is not in the match list
    '''

    ##Check that type is valid
    assert match_type in ['contains', 
                          'exact',
                          'starts_with',
                          'ends_with',
                          ],'Invalid match_type specified'
    new_tuple_list = []
    #Check whether we are looking at a specific index in the tuple

    ##This can be done multiple ways, think analysing the if once is the most efficient so justifies the additional typing
    if isinstance(tuple_index,int):
        new_tuple_list = [
                            list_item for list_item in tuple_list 
                            if not analyze_item_criteria(list_item[tuple_index],remove_list,match_type)
                        ]
    else:
        new_tuple_list = [list_item for list_item in tuple_list if not analyze_item_criteria('|'.join([str(x) for x in list_item])
        ,remove_list,match_type)]

    return new_tuple_list





def test():
    final_device = remove_from_tuple_list(device,remove_list,0,match_type = 'contains')
    print(remove_from_tuple_list(device,remove_list,0,match_type = 'contains'))
    print(remove_from_tuple_list(device,remove_list,0,match_type = 'exact'))
    remove_list = ['nvme1n1', 'nvme0n1p1', 'nvme0n1']
    print(remove_from_tuple_list(device,remove_list,0,match_type = 'ends_with'))
    print(remove_from_tuple_list(device,remove_list,0,match_type = 'starts_with'))


    print(remove_from_tuple_list(device,remove_list,None,match_type = 'contains'))
    print(remove_from_tuple_list(device,remove_list,None,match_type = 'exact'))
    remove_list = ['nvme1n1', 'nvme0n1p1', 'nvme0n1']
    print(remove_from_tuple_list(device,remove_list,None,match_type = 'ends_with'))
    print(remove_from_tuple_list(device,remove_list,None,match_type = 'starts_with'))

    '''
    Output:
    -------
    [('nvme2n1',), ('nvme3n1',)]
    [('nvme2n1',), ('nvme3n1',)]
    [('nvme2n1',), ('nvme0n1p128',), ('nvme3n1',)]
    [('nvme2n1',), ('nvme3n1',)]
    [('nvme2n1',), ('nvme3n1',)]
    [('nvme2n1',), ('nvme1n1', '/local'), ('nvme0n1p1', '/'), ('nvme0n1p128',), ('nvme3n1',)]
    [('nvme2n1',), ('nvme1n1', '/local'), ('nvme0n1p1', '/'), ('nvme0n1p128',), ('nvme3n1',)]
    [('nvme2n1',), ('nvme3n1',)]
    '''
Related