A function stores specific elements of a list in another list

Asked

Viewed 114 times

0

I want to make a function that takes a list of ordered numbers and two numbers (one bottom and one top) and returns a new list with the elements of the first list between the two numbers.

Example:

lista inicial=[12,14,15,16,18,20,24,26,28,32,34,38]  
limite inferior=13  
limite superior = 26  
lista exibida: [14,15,16,18,20,24,26]

What I tried to do:

def mudalista(l,li,ls):
    sublista=list()
    for el in l:
        if el>=li:
            pos_el1=l.index(el)
        elif el>=ls:
            pos_el2=l.index(el)
        sublista=l[pos_el1:pos_el2]     
    print(l[subslista])
    return

I tried to start the function with "for el in l" and when(if) the element was larger than the smaller number, stored in a variable, doing the same thing with the higher(elif), but when I try to make the second list, it makes a mistake.

Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    mudalista([12,14,15,16,18,20,24,26,28,32,34,38],13,26)
  File "C:\Users\G1820583\Downloads\exercicio lista.py", line 11, in mudalista
    sublista=l[pos_el1:pos_el2]
UnboundLocalError: local variable 'pos_el1' referenced before assignment
  • William, you know table test?

  • No... I’ll read about

  • Study Python list methods, more precisely how to pick up segments of it and, of course, the method index().

2 answers

5

TL;DR

import bisect

def intervalo(lista, i, j):
    left = bisect.bisect_left(lista, i)
    right = bisect.bisect(lista, j)

    return lista[left:right]

Explanation

Notice this section of your code:

if el >= li:
    pos_el1 = l.index(el)
elif el >= ls:
    pos_el2 = l.index(el)
sublista = l[pos_el1:pos_el2]

The code above has 3 possibilities:

  1. el >= li then pos_el1 is created, because entered the 1st if;
  2. el >= ls then pos_el2 is created because entered the 2nd if;
  3. el is smaller than both, li and ls, and no variable is created because it did not enter any if and you just breed them into these ifs.

Note that in no possible flow of this code the variables pos_el1 and pos_el2 may coexist, or only one exists, or only the other, never both are created in the same stream.

This causes the posterior line: sublista = l[pos_el1:pos_el2] must be an error, because you are using one of the variables that must not be created.

Possible solutions:


List comprehension (#Docs)

The simplest of all could be to use a comprehensilist on filtering only items that meet its condition. As your list has the ordered values, this solution works well:

def intervalo(lista, i, j):
    return [item for item in lista if i <= item <= j]

lista_inicial = [12,14,15,16,18,20,24,26,28,32,34,38]
intervalo(lista_inicial, 13, 26)
# [14, 15, 16, 18, 20, 24, 26]

If you still don’t know the concept of comprehensions, an equivalent version using for would look like this:

def intervalo(lista, i, j):
    resultado = []

    for item in lista:
        if if i <= item <= j:
            resultado.append(item)

    return resultado

lista_inicial = [12,14,15,16,18,20,24,26,28,32,34,38]
intervalo(lista_inicial, 13, 26)
# [14, 15, 16, 18, 20, 24, 26]

Slice (#Docs)

One can use Slices, as you did yourself, but it is necessary to treat when the searched values do not exist in the list, because if the values that limit your range do not exist in the list you will have to select the nearest index. A solution could be:

def intervalo_slices(lista, i, j):
    left, right = 0, len(lista)

    for index, item in enumerate(lista):
        if item < i:
            # se for menor que o mínimo mostra do próximo em diante
            left = index + 1
        if item <= j:
            # Se for maior que o máximo mostra até o próximo índice
            right = index + 1

    return lista[left:right]

The algorithm is simple:

  1. I create left and right with standard values 0 and the total size of the list (len(lista)), that way if the flow doesn’t enter any of the ifs the result will be lista[0:len(lista)] which is only a copy of the original list;
  2. Use enumerate to have the indexes of the items I am iterating and perform the calculations (similar to the i that we see in bonds for of other languages);
  3. Test in all iterations if item is less than the minimum. If the condition is true, it means that the lower limit of our list is the next item, as it will be equal to or greater than i;
  4. Test in all iterations if item is less than or equal to the maximum, if yes, the index I want is also the next, because Slices do not include the right value (ex: [0, 1, 2, 3][:2] results in [0, 1]);
  5. Finally return the Slice with the right values

If you give a print within the if will see that left and right receive values several times until the conditions are no longer true. It’s not a very optimized solution, but it’s a more "handmade" algorithm and I wanted to show what went wrong with your example with a possible solution to your line of reasoning.

For a more optimized solution in this same line of reasoning, see the next session.


Module bisect (#Docs)

Another way to solve the problem "which index of the list should I do Slice" is to use a binary search algorithm to find in which position of the list the Slice, even if the value does not exist in this list.

The module bisect was made to work with ordered sequences and binary search, so we can take advantage of a native python module to solve the problem with a complexity O(Log2 n) instead of O(n) like the previous problems.

For that we will use the functions bisect and bisect_left. Both have the same purpose and function, they serve to find out at which position of an ordered sequence a value must be inserted in order to maintain its ordering.

For example:

from bisect import bisect

lista = [0, 2, 4, 5, 8, 10]
index = bisect(lista, 3)
# 2

lista.insert(index, 3)
# lista = [0, 2, 3, 4, 5, 8, 10]
#                └── posição retornada por bisect

See that bisect returned the index 2, which is exactly where I can enter the value 3 on my list to maintain the ordering of the same.

But what’s the point of bisect_left?

To function bisect and bisect_left differ only when the value sought already exists in the sequence, that is, in the previous example could be used bisect or bisect_left because the result would be the same.

When we look for the index of an existing value in the sequence we have two options:

  1. Insert into the same index as the value found (bisect_left).

    To enter the value 1 on the list [0, 1, 2, 3]:

    #      ┌── valor encontrado
    [0, 1, 1, 2, 3]
    #   └── novo valor
    
  2. Insert into the next index of the value found (bisect).

    To enter the value 1 on the list [0, 1, 2, 3]:

    #   ┌── valor encontrado
    [0, 1, 1, 2, 3]
    #      └── novo valor
    

In summary, the difference is whether the returned index is the same as the found value or the next one. The code below illustrates the examples cited above:

from bisect import bisect, bisect_left

lista = [0, 1, 2, 3]
indice = bisect(lista, 1)
# 2
indice_left = bisect(lista, 1)
# 1

Final code using bisect

Now that you understand the concept, just use the bisect_left to correctly pick the minimum index to use on Slice and bisect to pick up the maximum index.

import bisect

def intervalo(lista, i, j):
    left = bisect.bisect_left(lista, i)
    right = bisect.bisect(lista, j)

    return lista[left:right]

Completion

These are some ways to do what you want, I presented the module bisect because besides being performatic and interesting, I think it is important for the learned to have examples of practical use.

I created this Repl.it with all examples running.

2

This error you presented occurs because the variable pos_el1 shall be created only if if el>=1. That is, if the program does not enter this block, you will try to use a variable that does not exist.

There are also some other logic errors such as block if el>=li. If the element is larger than li the minor element will be it. Only this means that the largest element in the list will also be the smallest, since the largest element is greater than li.

There is also a problem using Slice to get a new list of numbers. Remember that the final position is a stop position and the value at that position will not be returned. Soon you should make the final position the position of the largest element +1.

Look at this code below that I made:

def mudaLista(lista,li,ls):

    menor_elemento = None
    maior_elemento = None

    for elemento in lista:

        if not menor_elemento and elemento >= li:
            menor_elemento = elemento

        if elemento <= ls:
            maior_elemento = elemento

    if menor_elemento and maior_elemento:

        pos_menor = lista.index(menor_elemento)
        pos_maior = lista.index(maior_elemento)

        return lista[pos_menor:pos_maior+1]

    else:
        return []

lista = [num for num in range(100)] #Gera uma lista com números de 1 à 100
li = 43
ls = 57
print(mudaLista(lista,li,ls)) # Saída: [43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57]

Browser other questions tagged

You are not signed in. Login or sign up in order to post.