List index out of range

2

I am writing a code in Python that lists items in an alternative way. For example: [a, b, c], [1,2,3] → [a, 1, b, 2, c, 3]. But I get an error "List index out of range". This is my code:

def unir_listas_alterno(lista1,lista2):
    nuevaLista = []
    contadorIndice = len(lista1) + len(lista2)
    contador = 0
    while(contador <contadorIndice):
        nuevaLista.append(lista1[contador]) #AQUI DA ERROR  
        nuevaLista.append(lista2[contador])                          
        contador+=1
    return nuevaLista

lista1 = ["a","b","c"]
lista2 = [1,2,3]
print(unir_listas_alterno(lista1,lista2)) #AQUI DA ERROR
    
asked by Marco 13.11.2017 в 17:26
source

2 answers

1

The answer has a lot to do with the assumption that the lists have the same number of values or not.

If they have the same number of elements , the answer has been given to you by the problem you have in this statement: contadorIndice = len(lista1) + len(lista2) . contadorIndice is the variable that you will use to iterate "up", the problem is that in your example the value contadorIndice will be 6, then when the iteration reaches 4 the error will appear, simply because the lists have 3 elements only ( lista1[contador] ). The solution is simple, just iterate to the length of any of the lists, no matter which, in this case are the same, for example: while(contador <len(lista1))

On the other hand, if the lists do not have the same logic , the problem is a little more complex. To begin with, one should define what should be done in these cases, if the idea is to keep a list that combines only one element of the other, the iteration should be done up to contadorIndice = min(len(lista1),len(lista2)) that is to say the length of the list more girl. If, on the other hand, we want the final list to be the sum of both lists, we must make some additional modifications and here we should iterate up to contadorIndice = max(len(lista1),len(lista2)) . As an example of the latter, this would be the code:

def unir_listas_alterno(lista1,lista2):
    nuevaLista = []
    contadorIndice = max(len(lista1),len(lista2))
    contador = 0
    while(contador < contadorIndice):
        if len(lista1) > contador:
            nuevaLista.append(lista1[contador])
        if len(lista2) > contador:
            nuevaLista.append(lista2[contador])
        contador+=1
    return nuevaLista

lista1 = ["a","b","c"]
lista2 = [1,2,3,4]
print(unir_listas_alterno(lista1,lista2))

Of course there are much more compact ways of solving these situations, for example the use of zip_longest that basically will be combining element by element setting tuples of type ("a", 1), (b, "2"), ("c", 3), (None, 4) that then you simply have to "flatten" to turn everything into a list.

from itertools import izip_longest

lista1 = ["a","b","c"]
lista2 = [1,2,3, 4]

lista_final = [e for l in list(izip_longest(lista1, lista2)) for e in l if e]
print(lista_final)

> ['a', 1, 'b', 2, 'c', 3, 4]
    
answered by 13.11.2017 в 18:28
1

As very well indicated by lois6b in your comment contadorIndice should be equal to the length of the lists (both should have the same length), in your example it should be 3, then that line should be:

contadorIndice = len(lista1)

With that your code is correct as long as you make sure to pass two lists with the same number of elements.

There are many other more efficient and simple options in case they are interesting:

  • Use range with a for :

    def unir_listas_alterno(lista1,lista2):
        nuevaLista = []
        for i in range(len(lista1)):
            nuevaLista.append(lista1[i])
            nuevaLista.append(lista2[i])
        return nuevaLista
    
  • Using a for with enumerate :

    def unir_listas_alterno(lista1,lista2):
        nuevaLista = []
        for i,  e1 in enumerate(lista1):
            nuevaLista.append(e1)
            nuevaLista.append(lista2[i])
        return nuevaLista
    
  • Using zip next to list.extend :

    def unir_listas_alterno(lista1,lista2):
        nuevaLista = []
        for pair in zip(lista1, lista2):
            nuevaLista.extend(pair)
        return nuevaLista
    
  • Compression of lists with zip :

    def unir_listas_alterno(lista1,lista2):
        return [elemento for pareja in zip(lista1, lista2) for elemento in pareja]
    
  • My preferred option is to use the chain.from_iterable of the library itertools :

    import itertools
    
    def unir_listas_alterno(lista1, lista2):
        return list(itertools.chain.from_iterable(zip(lista1, lista2)))
    

You should consider validating the entry, making sure that both lists have the same length and throwing an exception otherwise. You can simply make something like the beginning of the function:

def unir_listas_alterno(lista1, lista2):
    if len(lista1) != len(lista2):
        raise ValueError("Ambas listas han de tener el mismo número de elementos")

Some measures of execution times (1000000 calls to the function with lists of 300 elements):

  

while:
  1000000 loops, best of 3: 55.43 sec per loop
for + range:
  1000000 loops, best of 3: 47.46 sec per loop
for + enumerate:
  1000000 loops, best of 3: 41.98 sec per loop
zip + extend:
  1000000 loops, best of 3: 23.40 sec per loop
zip in compression of lists:
  1000000 loops, best of 3: 24.28 sec per loop
chain.from_iterable + zip:
  1000000 loops, best of 3: 14.16 sec per loop

It is observed as itertools.chain.from_iterable is considerably more efficient with respect to the other options.

    
answered by 13.11.2017 в 19:28