Los arreglos o arrays son una de las estructuras de datos más usadas en programación. Proporcionan una forma de almacenar un número fijo de elementos en una secuencia.
En este artículo, aprenderás cómo implementar un arreglo en Python, incluyendo sus operaciones y características principales.
¿Qué es un arreglo o array?
Un arreglo es una colección de elementos, cada uno identificado por un índice entero. Las características fundamentales de un arreglo incluyen las siguientes:
- Tamaño fijo: El tamaño o número de elementos del arreglo se determina al crearlo y no se puede cambiar.
- Acceso aleatorio: Los elementos se pueden acceder directamente usando índices que comienzan en
0. - Homogéneo o heterogéneo: Los arreglos pueden almacenar elementos del mismo tipo de dato o de tipos mixtos, dependiendo de la implementación.
En Python, los arreglos no están integrados como una estructura de datos primitiva. La biblioteca estándar incluye el tipo array.array para trabajar con datos numéricos homogéneos, pero en este artículo implementarás tu propio arreglo usando el módulo ctypes para tener un mayor control de bajo nivel.
A través de ste ejercicio, podrás obtener una serie de habilidades fundamentales en Python, como puede ser el uso de clases y de métodos especiales, también conocidos como métodos mágicos o dunder.
Operaciones comunes en un arreglo
A continuación, un resumen de las operaciones que soportará tu arreglo:
| Operación | Descripción |
|---|---|
array = Array(size) |
Construye un arreglo que puede contener size elementos. |
array[index] |
Accede al elemento en index. |
array[index] = value |
Asigna value al elemento en index. |
array.clear([value]) |
Restablece todos los elementos del arreglo, asignándoles el valor value o None. |
len(array) |
Devuelve la longitud del arreglo. |
item in array |
Devuelve True si item está contenido en el arreglo y False si no lo está. |
for item in array: ... |
Itera sobre los elementos del arreglo. |
for item in reversed(array): ... |
Itera sobre los elementos en orden inverso. |
Implementación de un arreglo en Python
A continuación encontrarás la implementación del arreglo en Python:
import ctypes
from typing import Any, Generator, Optional
class Array:
def __init__(self, size: int) -> None:
if size <= 0:
raise ValueError("Array size must be a positive integer")
self._size = size
self._data = (ctypes.py_object * self._size)()
self._type: Optional[type] = None
self.clear()
def clear(self, value: Optional[Any] = None) -> None:
if self._type is not None and value is not None and not isinstance(value, self._type):
raise TypeError(f"Expected type {self._type}, got {type(value)}")
for i in range(self._size):
self._data[i] = value
def __len__(self) -> int:
return self._size
def __contains__(self, value: Any) -> bool:
for item in self._data:
if item == value:
return True
return False
def _check_index(self, index: int) -> None:
if not 0 <= index < self._size:
raise IndexError(
f"Index out of range: {index}. "
f"Valid range: 0 to {self._size - 1}"
)
def __getitem__(self, index: int) -> Any:
self._check_index(index)
return self._data[index]
def __setitem__(self, index: int, value: Any) -> None:
if self._type is None and value is not None:
self._type = type(value)
elif value is not None and not isinstance(value, self._type):
raise TypeError(f"Expected type {self._type}, got {type(value)}")
self._check_index(index)
self._data[index] = value
def __iter__(self) -> Generator[Any, None, None]:
yield from self._data
def __reversed__(self) -> Generator[Any, None, None]:
yield from reversed(self._data)
def __str__(self) -> str:
return f"{self.__class__.__name__}({', '.join(map(str, self._data))})"
def __repr__(self) -> str:
return f"{self.__class__.__name__}(size={self._size})"
El atributo ._size almacena el tamaño fijo del arreglo o el número de posiciones disponibles, mientras que ._data proporciona un arreglo de bajo nivel creado con ctypes.py_object * size, donde se guardan las referencias a los objetos de Python.
En ._type, se guarda el tipo de dato de los elementos del arreglo. Una vez que asignas el primer valor no nulo, solo se permiten valores de ese mismo tipo.
El método .clear() recorre todas las posiciones del arreglo y les asigna value o None. Si ._type ya está fijado, comprueba que value coincida con ese tipo.
Los métodos especiales te permiten ofrecer las funcionalidades siguientes:
.__getitem__(): devuelve el valor almacenado enindexdespués de verificar que el índice esté dentro del rango válido..__setitem__(): fija el tipo de dato de los elementos del arreglo con el primervalueno nulo. A partir de ahí, solo permite valores de ese tipo. También valida que el índice esté en rango antes de asignar.__len__: devuelve el tamaño del arreglo o el número de elementos.__contains__(): comprueba sivalueestá contenido en el arreglo y devuelveTrueoFalse.__iter__y__reversed__: permiten iterar sobre los elementos del arreglo en orden normal o inverso.__str__y__repr__: devuelven representaciones de cadena que muestran el contenido actual del arreglo.
Ejemplo de uso del arreglo
En esta sección verás ejemplos prácticos de cómo crear, inicializar y manipular instancias de tu clase Array.
Inicialización
Para crear un Array, solo necesitas indicar su tamaño. Todos los elementos se inicializan con None por defecto:
>>> a = Array(5) # Crea un arreglo con 5 elementos
>>> print(a)
Array(None, None, None, None, None)
Acceso y modificación de elementos
Puedes leer y escribir elementos con el operador de indexado [index], igual que en una lista:
>>> a[0] = 42 # Establece el primer elemento a 42
>>> a[0]
42
Si intentas asignar un valor de un tipo de dato diferente al que ya tiene el arreglo, o usar un índice fuera del rango válido, obtendrás errores claros:
>>> a = Array(3)
>>> a[0] = 1
>>> a[1] = 2
>>> a[2] = "tres"
Traceback (most recent call last):
...
TypeError: Expected type <class 'int'>, got <class 'str'>
>>> a[3] = 10
Traceback (most recent call last):
...
IndexError: Index out of range: 3. Valid range: 0 to 2
Limpiar el arreglo
El método .clear() restablece todos los elementos asignándoles el valor especificado. El valor predeterminado es None:
>>> a = Array(5)
>>> a.clear(0) # Establece todos los elementos a 0
>>> print(a)
Array(0, 0, 0, 0, 0)
Longitud y pertenencia
El método .__len__() devuelve el tamaño del arreglo, mientras que .__contains__() verifica si un elemento existe en el arreglo:
>>> a = Array(5)
>>> len(a)
5
>>> 42 in a
False
>>> a[0] = 42
>>> 42 in a
True
Iteración
Los métodos .__iter__() y .__reversed__() permiten iterar sobre el arreglo tanto hacia adelante como hacia atrás:
>>> a = Array(5)
>>> a[0] = 1
>>> a[1] = 2
>>> a[2] = 3
>>> a[3] = 4
>>> a[4] = 5
>>> for item in a:
... print(item)
...
1
2
3
4
5
>>> for item in reversed(a):
... print(item)
...
5
4
3
2
1
Representación en cadena
Los métodos .__str__() y .__repr__() son más bien utilitarios y proporcionan representaciones legibles para humanos y amigables para desarrolladores:
>>> a = Array(5)
>>> a.clear(0)
>>> print(a)
Array(0, 0, 0, 0, 0)
>>> print(repr(a))
Array(size=5)
Conclusión
Has aprendido cómo implementar un arreglo de tamaño fijo que proporciona operaciones esenciales como indexación, iteración y pertenencia, al tiempo que refuerza la consistencia del tipo de dato de sus elementos. Este conocimiento te sirve de base para entender estructuras de datos fundamentales en Python.