Eliminar elemento repetido de una lista en Go

Las listas en Go son contenedores de tamaño dinámico que permiten almacenar un número variable de elementos de un mismo tipo, pueden expandirse o reducirse cuando se añade o elimina un elemento.

Descripción del tema: dada una lista de números (algunos repetidos), se le pregunta al usuario cuál desea eliminar y el programa debe eliminar todos los elementos de la lista que sean igual al número ingresado por el usuario. El programa debe mostrar en pantalla primero la lista actual con los elementos que posee.

Código fuente
package main

import (
	"container/list"
	"fmt"
)

func main() {
	c := []int{2, 90, 89, 2, 5, 101, 90, 35, 2, 43, 89, 101, 5, 89, 90, 5}
	lista := list.New()

	var item int

	for _, v := range c {
		lista.PushBack(v)
	}

	fmt.Print("Lista actual:     [ ")
	for v := lista.Front(); v != nil; v = v.Next() {
		fmt.Printf("%d ", v.Value)
	}
	fmt.Println("]")

	print("\nIngrese el elemento a eliminar: ")
	fmt.Scanf("%d", &item)

	for v := lista.Front(); v != nil; v = v.Next() {
		if b := v.Prev(); b != nil && v.Value == item {
			lista.Remove(v)
			v = b
		} else if v.Value == item {
			b := v.Next()
			lista.Remove(v)
			v = b
		}
	}

	fmt.Print("Lista modificada: [ ")
	for v := lista.Front(); v != nil; v = v.Next() {
		fmt.Printf("%d ", v.Value)
	}
	fmt.Print("]\n")
}

El paquete ‘container/list’ es el que nos permite trabajar con listas en Go y es bastante pequeño. A continuación explicare como funciona este pequeño programa y que labor cumple cada método en las listas.

Importando paquetes
import (
	"container/list"
	"fmt"
)

Lo primero que debemos hacer si queremos trabajar con listas en Go, es importar el paquete list, como se ha hecho en la línea #2. El paquete fmt nos sirve para leer e imprimir datos ingresados por el usuario.

Creando array estático
c := []int{2, 90, 89, 2, 5, 101, 90, 35, 2, 43, 89, 101, 5, 89, 90, 5}

Este array estático se ha creado para añadir fácilmente elementos a la lista, ahorrando líneas de código como se verá más adelante. El array contiene varios números, la mayoría repetidos.

Creando el objeto lista
lista := list.New()

En Go las listas se crean usando el nombre del paquete, seguido de la función New(). Esto crea una nueva lista vacía.

Creando la variable item
var item int

La variable item se ha creado para almacenar el elemento que el usuario desea eliminar de la lista, cuyo valor será ingresado por el mismo usuario más adelante.

Insertando valores del array «c» en la lista
for _, v := range c {
    lista.PushBack(v)
}

Con un bucle for vamos a recorrer el array «c» que contiene elementos de tipo int, cada elemento será insertado en la lista «lista». Para recorrer el array usamos la palabra clave range, la cual nos devuelve 2 valores, el primero es el índice del elemento (0, 1, 2, 3, …) y el segundo es el elemento; usamos el «_» para indicarle Go que vamos a ignorar el índice y cada elemento lo asignamos a la variable «v». Con el método .PushBack() insertamos cada elemento al final de la lista.

Mostrando los elementos de la lista actual
fmt.Print("\nLista actual:     [ ")
for v := lista.Front(); v != nil; v = v.Next() {
    fmt.Printf("%d ", v.Value)
}
fmt.Println("]")

La función Print() del paquete fmt en la primera línea solo imprime el texto "Lista actual [", nada más. Vamos a recorrer la lista con el bucle for usando los métodos .Front() para obtener el primer elemento de la lista y .Next() para avanzar al siguiente elemento de la lista en cada iteración; se asigna el primer elemento en la variable «v» y luego se verifica que no sea nil. La función Printf() imprime cada elemento de la lista, el primer argumento «%d» le indica a la función que el elemento es de tipo decimal y el segundo argumento v.Value, obtiene el elemento a imprimir.

La última función Println() lo único que hace es imprimir el carácter «]» y dar un salto de línea.

Solicitando y leyendo valor ingresado por el usuario
print("\nIngrese el elemento a eliminar: ")
fmt.Scanf("%d", &item)

Con la función print() le solicitamos al usuario que le indique al programa qué elemento desea eliminar. Con la función Scanf() del paquete fmt, leemos el valor ingresado por el usuario y lo almacenamos en la variable item; pero la función recibe dos parámetros, el primero («%d») le indica al programa que deseamos leer un valor de tipo decimal y el segundo es la variable en donde deseamos almacenar dicho valor.

Eliminar el elemento y todos los que coincidan
for v := lista.Front(); v != nil; v = v.Next() {
    if b := v.Prev(); b != nil && v.Value == item {
        lista.Remove(v)
        v = b
    } else if v.Value == item {
        b := v.Next()
        lista.Remove(v)
        v = b
    }
}

Ya sabemos lo que hacen los métodos .Front() y .Next(); pero para eliminar un elemento de una lista en Go y todos los elementos repetidos, hay que hacer un proceso diferente que involucra a los métodos .Prev() y .Remove(), así como una variable auxiliar que en este caso se ha llamado «b».

La sentencia if está divida en dos partes por un «;», en la primera usamos .Prev() para obtener el elemento anterior de «v» en la lista; por ejemplo, supongamos que tenemos una lista con los siguientes valores:

[5, 13, 22, 10, 2]

Y «v» tiene el valor de 22, quiere decir que el valor anterior es 13 y se asigna a la variable «b»; en la segunda hay una expresión booleana en la que se verifica que «b» sea diferente a nil (nulo) y que item sea igual al elemento en v.Value. Si la expresión booleana es true, entonces se elimina el elemento usando .Remove() y se asigna el elemento de «b» nuevamente a «v» para que continúe desde dicho valor.

else if v.Value == item {
    b := v.Next()
    lista.Remove(v)
    v = b
}

La sentencia else if es diferente a la sentencia if. En este caso solo se evalúa si el valor en e.Value es igual al de item y en caso de ser true, en lugar de obtener el valor anterior de «v» con .Prev(), vamos a obtener el siguiente valor de «v» con .Next() y lo asignamos a la variable «b»; lo demas es igual a la sentencia if.

¿Para qué obtenemos el valor anterior y el siguiente de la variable «v»?

La razón es muy sencilla, es para evitar acceder a espacios en memoria fuera de rango en la lista al verificar que los valores en v.Value no sean nulos cuando usamos los métodos .Next() y .Prev(). Funciona de la siguiente forma, supongamos que tenemos la siguiente lista:

[5, 13, 22, 10, 2]

Y deseamos eliminar el número 5 o el número 2. Si accedemos al valor anterior a 5 con .Prev() estaríamos accediendo a la nada ya que estamos saliendo del rango en la lista y por lo tanto v.Value seria nulo, esto quiere decir que jamás se ejecutará el código en la sentencia if sino la sentencia else if en el cual accedemos al siguiente valor de 5 que seria 13.

Si accedemos al siguiente valor de 2 con .Next() vamos a tener el mismo inconveniente, nos saldremos del rango en la lista y v.Value sería nulo; en este caso si se ejecutara el código en la sentencia if debido a que el valor anterior de 2 no es nulo sino 10. En ambos casos el bucle continua en el valor que «v» recibe de «b» en la asignación v = b, para el primer ejemplo continúa en 13 y en el segundo continúa en 10.

Mostrando la lista modificada
fmt.Print("Lista modificada: [ ")
for v := lista.Front(); v != nil; v = v.Next() {
    fmt.Printf("%d ", v.Value)
}
fmt.Print("]\n")

No es necesario explicar este fragmento de código ya que es igual al que usamos para mostrar la lista sin modificar y ya sabemos para que sirven .Front() y .Next().

Ejecutando nuestro programa

Al ejecutar nuestro programa veremos un resultado similar al siguiente:

[fixedtorres@linuxero]$ ./EliminarElementoDeLista
Lista actual:     [ 2 90 89 2 5 101 90 35 2 43 89 101 5 89 90 5 ]

Ingrese el elemento a eliminar: 2
Lista modificada: [ 90 89 5 101 90 35 43 89 101 5 89 90 5 ]
[fixedtorres@linuxero]$ 

Aquí hicimos la prueba eliminando el número 2, el cual está repetido 3 veces. Un detalle a observar es que al inicio de la lista aparece un número 2, esto nos indica que el primer código en ejecutarse fue el de la sentencia else if.

Añadir un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *