Delete elements in two lists based on the value that element has in that position in one of them

0

I am creating a program that loads data into two lists, diámetro and altura , and I have to delete the data from both lists when the second list, altura , has the value 0.

This is the code:

a=0
for i in diametro:
    for j in altura:
        if a == 0:
            diametro.pop(a)
            altura.pop(a)
     print(diámetro, altura)
     a=a+1

And it throws me an error in if a == 0 , says:

  

List index out of range

I do not know why this error appears to me.

    
asked by Lyan Rocio Mosquera Garcia 22.04.2018 в 20:08
source

3 answers

1

It follows that you have two lists with the same number of elements and you want to eliminate the elements in a certain position in both lists when in altura the value in that position is 0. The first error in this case is that you must go the lists at par, not two for nested (the second list is covered in full by each element of the first).

Also, you have to take two things into account if you intend to do the in-place removal:

  • Never you must modify the length of a list or any other iterable one (neither add nor add elements) when iterating with a for in .

  • It's obvious, but keep in mind that when you remove an item from the list, the indexes of the items after it become one less , so you have to consider this. If we use the typical for i in range(len(lista)) we must take into account that the length of the list will change, you indicate that before they were valid at the moment we eliminated an element they are not anymore.

That said, we can do what you want by modifying the lists or creating new objects:

  • In-place:

    A possible solution is to iterate over height and create a list that stores the indexes of the elements that are 0. Then we use a for to iterate over it and with del remove these indexes from both lists. You have to correct the indexes to take into account the second previous point:

    # Metodo 1
    filtro = [i-n for i, n in enumerate((i for i, a in enumerate(altura) if not a))]
    
    for i in filtro:
        del diametro[i]
        del altura[i]
    

    The part i-n for i, n in enumerate of the list by compression is responsible for correcting the index by subtracting to each index the number of elements previously eliminated when it is reached, it would be equivalent to:

    # Metodo 2
    filtro = [i for i, a in enumerate(altura) if not a]
    
    e = 0
    for i in filtro:
        del diametro[i-e]
        del altura[i-e]
        e += 1
    

    Another possibility is to use range + len to generate the indexes but iterating from right to left:

    # Metodo 3
    for i in range(len(altura) - 1, -1, -1):
        if not altura[i]:
            del altura[i]
            del diametro[i]
    
  • Creating two new lists:

    # Metodo 4
    filtro = [i for i, a in enumerate(altura) if a]
    diametro = [diametro[i] for i in filtro]
    altura = [altura[i] for i in filtro]
    

    A different option is to use zip , although it is somewhat less idiomatic:

    # Metodo 5
    diametro, altura = (list(l) for l in zip(*((d, a)
                                for d, a in zip(diametro, altura) if a)))
    

    Finally, the most efficient option I can think of is to use a boolean mask, NumPy style. We can use itertools.compress to apply it:

    # Metodo 6 
    import itertools
    
    filtro_boleano = [a != 0 for a in altura]
    diametro = list(itertools.compress(diametro, filtro_boleano))
    altura = list(itertools.compress(altura, filtro_boleano))
    
  

Note: list.pop removes and returns the item in the index that is passed to it. If you are not going to use the eliminated value it is better to use del for such purposes.

In all cases for example:

diametro = [4, 8, 7, 2, 3, 6, 5, 9, 6, 8] 
altura   = [0, 0, 6, 0, 1, 0, 3, 4, 8, 0]

We get:

  

[7, 3, 5, 9, 6]
  [6, 1, 3, 4, 8]

IMPORTANT

Removing an item from a list is a costly operation , usually since it involves rearranging the rest of the elements (as is the case with list.insert ). If we do not have memory problems and prioritize the execution time, it may be important to choose to create new lists instead of removing the items directly, especially if we create the lists by compression. Taking this to the extreme, for lists of one million elements, with the elimination of 50% of the items we have the following execution times as reference for the methods shown above in the same order:

  

Method 1: 97.64361238479614 seconds

     

Method 2: 304.90778708457947 seconds !!!!

     

Method 3: 98.10549569129944 seconds

     

Method 4: 0.1168980598449707 seconds

     

Method 5: 0.34819936752319336 seconds

     

Method 6: 0.0786902904510498 seconds

The difference is abysmal and shows how the best method of all is 6 in terms of execution time followed closely by 4, at the expense of using much more memory.

    
answered by 22.04.2018 в 21:44
0

I hope you're well

The key is how you cycle your for cycle, the function range() and len() will be very helpful for you to go through lists using an iteration cycle

for i in range(len(diametro)):
  for i in range(len(altura)):
    if altura[i] == 0:
       diametro.pop(i)
       altura.pop(i)
print(diametro, altura)

If in position i of your height list you find a data with value equal to 0, that position will be removed from both the diameter list and the height list.

I hope you have been helped, you will find a better explanation about the functions range() and len() here and here .

    
answered by 22.04.2018 в 20:48
0

First you must bear in mind that the pop function only works with lists, in your code there is the variable a which is a number or int so you can not apply this function, now with this clear we can change the code as follows:

for i in range(len(diametro)): 
       for j in range(len(altura)): 
              if altura[j] == 0:          
                  diametro.pop(j)       
                  altura.pop(j)    
       print(diametro, altura) 

Now if you go out of range in your code, there is only one possibility, that when you do diameter.pop (a) the list is empty, otherwise it is impossible to mark an error of these.

There is another alternative with your code and that is that you want to delete when height has a zero, if you want to remove the item from the height list and from the list > distance (which may not contain this number) you only need to put it with the variable j and nothing else, because j is the variable that goes through the list preventing it from going out of range, unless the 2 lists are of different length, then the code would look like this:

for i in range(len(diametro)): 
           for j in range(len(altura)): 
                  if altura[j] == 0:          
                     diametro.pop(j)       
                     altura.pop(j)    
           print(diametro, altura) 

With this you guarantee to eliminate the element in the height list, and if you want to eliminate the zero from the diameter list you should add a if that says the same as the first if but with diameter, that is:

for i in range(len(diametro)): 
           for j in range(len(altura)): 
                  if diametro[j]==0:          
                     diametro.pop(a)  
                 if altura[j]==0:   
                      altura.pop(a)  
           print(diametro, altura) 
  

Keep in mind that it is not necessary to make an external variable to know the index where I must remove an element, the range function already does it for you.

    
answered by 23.04.2018 в 03:59