Python: Manipulação de Listas e Matrizes
Gustavo Soares
Posted on May 12, 2023
Listas
Python possui uma estrutura de dados correspondente ao array em linguagens C-like: a lista.
As listas e matrizes são estruturas de dados que permitem armazenar um conjunto de valores em uma única variável. Essas estruturas são essenciais em Python e em outras linguagens de programação, pois permitem o armazenamento e manipulação de dados de forma eficiente e organizada.
As listas são usadas para armazenar uma sequência de elementos, permitindo que sejam acessados, adicionados ou removidos conforme necessário. Já as matrizes são uma extensão das listas, permitindo que sejam armazenados valores em uma tabela de duas ou mais dimensões. Uma matriz numa linguagem de programação se assemelha consideravelmente ao conceito de matriz na matemática, mas não com sob a mesma ótica.
Uma lista em Python é definida entre colchetes [ ]
, e pode conter qualquer tipo de dado, incluindo strings, números, outros objetos e até outras listas. Por exemplo:
minha_lista = [1, 2, 3, "quatro", "cinco"]
No entanto, utilizar listas de mais de um tipo não é considerado uma boa prática, principalmente quando se fala de linearidade de memória. Sabendo que uma lista é uma sequência contínua em memória, temos uma lista de 5 inteiros onde cada inteiro ocupa 4 bytes, logo, . Se uma mesma lista possuir elementos de diferentes tipos, isso dificulta o armazenamento e diminui a eficiência da mesma. Se isso não for o suficiente para que você aceite essa boa prática, ao usar listas de diferentes tipos, você pode enfrentar diferentes erros de tipagem, visto que não há como mapear todos os erros de tipo presentes em listas heterogêneas.
Para acessar elementos específicos da lista, utilizamos a indexação, que começa em 0. Por exemplo, para acessar o primeiro elemento da lista acima, usamos:
print(minha_lista[0])
## Output:
1
Métodos de listas
Métodos são funções ligadas a tipos, objetos ou pacotes. No caso das listas, são funções que fazem uso das informações internas das mesmas para executar suas sub-rotinas. Os métodos das listas são majoritariamente usados para a sua manipulação, ou seja, alteração de elementos, mapeamento e afins. Dentre os métodos, temos:
list.append(value)
O método append
adiciona o valor do parâmetro value
no final da lista que invocou o método.
intro = ["mansão", "thug"]
intro.append("stronda")
print(intro)
Output:
$> python3 <nome_do_arquivo>.py
['mansão', 'thug', 'stronda']
list.remove(value)
O método remove
remove (não me diga) a primeira ocorrência do elemento value
na lista que o invocou. Por “primeira ocorrência”, digo a primeira vez em que o elemento aparece, já que os valores dos elementos podem aparecer repetidamente numa mesma lista.
dezenas = [10, 20, 30, 50, 40, 10]
dezenas.remove(10)
print(dezenas)
Output:
$> python3 <nome_do_arquivo>.py
[20, 30, 50, 40, 10]
list.count(elem)
O métoro count
conta (não me diga???) quantas vezes o elemento elem
apareceu na lista que o invocou. Veja o exemplo:
repetidos = [1, 2, 3, 1, 2, 1, 2, 3, 2, 1]
print(repetidos.count(1))
Output:
$> python3 <nome_do_arquivo>.py
4
list.sort(reverse=False, key=None)
Onde:
-
reverse: bool
diz se a ordenação da lista deve ser crescente ou decrescente. Esse parâmetro recebe por padrão o valorFalse
, portanto, é opcional; -
key: callable
é a função que especifica o critério de ordenação. Esse parâmetro recebe por padrão o valorNone
, portanto, é opcional;
O método sort
ordena a lista que o invocou de acordo com os parâmetros especificados acima. Se nenhum valor for passado aos parâmetros, então a lista sofre uma ordenação crescente sem nenhum critério extra, apenas se baseando no tipo dos elementos da lista.
Observação: os parâmetros da função DEVEM ser passados de forma nomeada.
Veja o exemplo abaixo.
sequencia_numerica = [2, 6, 1, 3, 8, 7, 4]
sequencia_numerica.sort()
print(sequencia_numerica)
sequencia_numerica.sort(reverse=True)
print(sequencia_numerica)
nomes = ["gustavo", "joão", "gabriela", "maria", "ana"]
nomes.sort(key=len)
print(nomes)
Output:
$> python3 <nome_do_arquivo>.py
[1, 2, 3, 4, 6, 7, 8]
[8, 7, 6, 4, 3, 2, 1]
['ana', 'joão', 'maria', 'gustavo', 'gabriela']
No trecho de código acima, temos duas listas: sequencia_numerica
, de inteiros, e nomes
, de strings. De imediato, percebemos 3 chamadas diferentes ao método sort
, onde a primeira é uma ordenação simples e crescente, a segunda é uma ordenação decrescente e a terceira, uma crescente com critérios. O parâmetro key pode receber qualquer função que seja válida para o contexto da sua lista. Por exemplo, não podemos passar a função len
, que mede o tamanho de uma sequẽncia, como valor de key
para uma lista de inteiros, pois não há elementos nessa lista que se adequem ao requisito de len
. Logo, o que essa função faz nesse contexto é ordenar as strings de forma crescente pelo seu tamanho.
Slices
Slices em Python são uma forma de selecionar partes específicas de uma lista, matriz ou sequência. Essa funcionalidade é extremamente útil para a manipulação de dados, permitindo que sejam selecionados elementos específicos da estrutura de dados de forma rápida e eficiente. No entanto, apesar de sua utilidade, o uso de slices pode ser um pouco confuso para programadores de primeira viagem.
Slices são denotados pelo uso de colchetes []
e dois pontos :
para especificar a faixa de elementos que você deseja selecionar. Como se fossemos selecionar um elemento da lista pelo seu índice, mas são usados dois pontos.
A sintaxe geral é a seguinte: objeto[inicio:fim:passo]
, onde inicio
é o índice inicial da seleção, fim
é o índice final (exclusivo) da seleção e passo
é a quantidade de elementos a serem pulados. Veja o exemplo abaixo:
sequencia_numerica = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
primeiros_cinco = sequencia_numerica[:5]
pares = sequencia_numerica[::2]
print(sequencia_numerica)
print(sequencia_numerica[::])
print(primeiros_cinco)
print(pares)
No código acima, vemos a variável sequencia_numerica
, que recebe uma lista de inteiros de 0 a 10, e abaixo mais duas variáveis, também listas, derivadas da primeira que utilizam o slicing para gerar novas listas a partir de uma original. Seguindo a sintaxe descrita, temos a lista primeiros_cinco
, que recebe uma slice de sequencia_numerica
que vai da posição inicial 0 (omitida) até a posição final 5 (explícita) com o passo de 1 (também omitido e não pula nenhum índice). Já variável pares
recebe uma slice na qual tanto o inicio quanto o fim estão omitidos, ou seja, do começo da lista até o fim dela, e possui um passo de 2 (seleciona os índices de 2 em 2). Quanto algum dos valores é omitido, um valor padrão é considerado no lugar; para cada posição temos 0 para o início, o tamanho da lista para o final e 1 para o passo. O resultado do código acima é:
$> python3 <nome_do_arquivo>.py
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[0, 1, 2, 3, 4]
[0, 2, 4, 6, 8, 10]
List Comprehension
Em Python existem várias maneiras de criar listas. A maneira q será vista nesse tópico é a compreensão de lista (ou list comprehension para os amantes de inglês). List comprehensions permitem criar listas de uma forma mais concisa e legível, tornando o código mais fácil de ser entendido e mantido. Nomezinho chique, hein?
Essa funcionalidade permite criar uma nova lista a partir de uma lista existente, um loop ou outra estrutura de dados, aplicando uma expressão a cada elemento.
A sintaxe de uma List Comprehension pode ser entendida como:
[<expressao> for item in list] # ou
[<expressao> for i in range(inicio, fim)]
Traduzindo para o português bom e claro: "Aplique expressão
para todo item
da lista list
" e "Aplique expressão
para cada índice i
do intervalo estabelecido".
Onde:
-
expressao
é qualquer sentença da linguagem que retorne um valor. Por exemplo, elevar ao quadrado retorna o resultado da potência; -
item
representa cada elemento da lista a ser formada/manipulada. A expressão precisa ser compatível com o elemento. -
list
é a lista a ser criada/manipulada. Por mais que a lista já exista, essa manipulação criará uma nova lista, pois estamos copiando a lista original e modificando cada elemento de acordo com a expressão; -
i
é o índice do for loop. O índice aumenta o valor em 1 a cada vez que a iteração acontece até chegar no valor final;
Por exemplo, dado o seguinte código:
lista = []
for el in range(1, 10):
lista.append(el * 10)
É possível reescrevê-lo utilizando list comprehensions na forma:
lista = [el*10 for el in range(1, 10)]
Ambos os trechos de código têm o mesmo resultado, que é criar num intervalo de 1 a 9 (10 é exclusivo), onde cada elemento deve ser multiplicado por 10.
Você pode, ainda, inserir condicionais na forma de instruções if
em list comprehensions para limitar seu set de resultados. Por exemplo:
pares = [el for el in range(10) if el % 2 == 0]
print(pares)
Output:
$> python3 <nome_do_arquivo>.py
[0, 2, 4, 6, 8]
A list comprehension do código acima gera uma lista de todos os números pares no intervalo de 0 a 9 (10 exclusivo) graças à instrução if el % 2 == 0
.
No começo, pode parecer que você faz uma troca de legibilidade por eficiência e praticidade, mas com o passar do tempo, a sintaxe de list comprehensions começará a se impregnar na sua leitura e a memória muscular ajudará bastante nisso. Apenas pratique e aumente a dificuldade das comprehensions gradativamente.
Matrizes
Uma matriz pode ser entendida como uma lista que contém listas. Na interpretação matemática, uma lista é um vetor e uma matriz é um conjunto de
vetores representando uma matriz n-dimensional. A representação de uma matriz em python é dada da seguinte forma:
matriz = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Em vez de usar inteiros ou strings, você usa listas separadas por vírgula dentro de outra lista para representar uma matriz. Toda matriz possui as mesmas propriedades das listas, incluindo métodos, slices e list comprehensions.
Ao criar matrizes, você insere listas dentro de listas. As list comprehensions seguem o mesmo princípio: é só você inserir comprehensions dentro de comprehensions. Por exemplo, olhe o código abaixo:
matriz = [[i+j for j in range(3)] for i in range(1, 10, 3)]
print(matriz)
Isso criará uma matriz quadrada de ordem 3 idêntica à matriz do primeiro exemplo. Vamos dividí-la em partes para entendermos melhor. Olhando de fora para dentro:
-
for i in range(1, 10, 3)
é responsável por iterar sobre as linhas da matriz. Ele começa em 1, termina em 9 (10 exclusivo) e avança de 3 em 3. Ou seja, ele itera sobre os valores 1, 4 e 7, que correspondem às posições das três linhas na matriz; - A variável
i
representa a linha da matriz no loop; -
for j in range(3)
é responsável por iterar sobre as colunas da matriz. Ele itera sobre os valores 0, 1 e 2, que correspondem às posições das três colunas na matriz. - A expressão
i+j
dentro da list comprehension mais aninhada calcula o valor a ser adicionado na matriz. Essa expressão faz a soma de i, que representa a linha da matriz, com j, a coluna.
Iteração e Manipulação de Matrizes
Para percorrer matrizes, seguimos a mesma lógica de quando precisamos percorrer uma lista: fazer um loop. Mas se eu quero acessar cada linha e coluna da matriz, vou precisar de mais do que só um loop. No caso das matrizes, no algoritmo mais básico de iteração, serão necessários loops para uma matriz n-dimensional para acessar todos os seus elementos.
Bom, loops são simples, mas e se precisarmos manipular a matriz em operações como produto escalar, multiplicação entre matrizes e inversão? Vamos implementar na mão?
Para tal, usaremos a biblioteca numpy
instalada no último material. Esta seção será fortemente baseada na documentação de NumPy, então é extremamente recomendado usar ambos como base.
A biblioteca numpy
usa como base o elemento array
, popularmente chamado ndarray (n-dimensional array), uma implementação própria da biblioteca similar às listas de Python, com a diferença de que são obrigatoriamente homogêneos. Arrays e listas compartilham diversos comportamentos, como slicing e indexação.
Veja o exemplo abaixo:
import numpy as np
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
Aqui, criamos um array de Numpy através da função np.array
. O parâmetro dessa função pode ser uma lista de Python ou uma matriz. No caso acima, criamos uma matriz quadrada de ordem 3, onde, na linguagem Numpy, suas linhas e colunas são chamadas axes ou, no português claro, eixos. Logo, o primeiro axis dessa matriz possui tamanho 3 e o segundo, 3. Podemos, ainda, criar um array de tamanho n
preenchido por zeros, uns ou qualquer outro valor através das funções np.zeros(n)
, np.ones(n)
e np.full(n, v)
, respectivamente. Para criar um array que replique a função range, podemos utilizar a função np.arange()
, que tem a mesma funcionalidade de range()
. Para gerar arrays com valores igualmente distanciados entre si, podemos utilizar o método array.linspace(start, stop, num=50
, que gera num
números de um intervalo de start
até stop
exclusivo.
Quanto as propriedades do array, temos três principais: número de dimensões, tamanho e formato. O número de dimensões é expresso pela propriedade array.ndim
e retorna a quantidade de dimensões/eixos que o seu array possui (lembrando que um array pode ser um vetor ou uma matriz, é só uma nomenclatura utilizada pela biblioteca), o tamanho, pela propriedade array.size
, que retorna o produto do número de linhas pelo número de colunas e o formado, pela array.shape
, que retorna uma tupla de
inteiros indicando o número de elementos armazenados nas
dimensões do array.
Por exemplo:
import numpy as np
odeio_matriz = np.array([[5, 2, 9], [8, 10, 13], [15, 16, 1]])
print(odeio_matriz.size)
print(odeio_matriz.ndim)
print(odeio_matriz.shape)
Output:
$> python3 <nome_do_arquivo>.py
9
2
(3, 3)
No que tange a manipulação de arrays, temos métodos como array.transpose()
, que faz a transposta do array que o invocou. Dentre estes métodos, temos:
np.inner(a, b)
Dados dois vetores a e b de mesmo tamanho, inner
executa o produto interno entre a e b.
import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print("O produto interno é:", np.inner(a, b))
Output:
$> python3 <nome_do_arquivo>.py
O produto interno é: 32
np.dot(a, b)
Dadas duas matrizes a e b, dot
executa a multiplicação entre a e b. Para que essa operação ocorra com sucesso, temos que garantir que o número de colunas de a deve ser igual ao número de linhas de b.
import numpy as np
a = np.array([[2, 4, 6], [1, 5, 2], [9, 7, 1]])
b = np.array([[10, 11, 3], [8, 16, 1], [5, 9, 7]])
print("A multiplicação resulta em:\n", np.dot(a, b))
Output:
$> python3 <nome_do_arquivo>.py
A multiplicação resulta em:
[[ 82 140 52]
[ 60 109 22]
[151 220 41]]
array.trace()
O trace ou, no português, traço retorna a soma da diagonal principal da matriz que o invocou.
import numpy as np
a = np.array([
[2, 4, 6],
[1, 5, 2],
[9, 7, 1]
])
print("O traço da matriz 'a' é:", a.trace())
Output:
$> python3 <nome_do_arquivo>.py
O traço da matriz 'a' é: 8
np.linalg.det(a)
Dada uma matriz quadrada a, seu determinante pode ser calculado através da função det
, localizada dentro do pacote linalg
de NumPy. Se o determinante resultar em 0, significa que a matriz não admite inversa. Comumente, det
retornará um número de ponto flutuante que atinge o limite de precisão de Python. Nesses casos, é válido utilizar a função np.round()
para arredondar o resultado ao seu inteiro mais próximo.
import numpy as np
a = np.array([
[2, 2, 1],
[1, 3, 1],
[1, 2, 2]
])
determinante = np.linalg.det(a)
print("O determinante de 'a' é:", np.round(determinante))
Output:
$> python3 <nome_do_arquivo>.py
O determinante de 'a' é: 5.0
Se não tivessemos arredondado o determinante, seu resultado seria .
np.linalg.inv(a)
Dada uma matriz quadrada a, o inverso de a pode ser calculado através da função inv
, também dentro de linalg
. Como a inversão de uma matriz depende do determinante da mesma ser diferente de 0, usaremos a matriz do exemplo anterior.
import numpy as np
a = np.array([
[2, 2, 1],
[1, 3, 1],
[1, 2, 2]
])
inversa = np.linalg.inv(a)
print("A inversa de 'a' é:\n", inversa)
Output:
$> python3 <nome_do_arquivo>.py
A inversa de 'a' é:
[[ 0.8 -0.4 -0.2]
[-0.2 0.6 -0.2]
[-0.2 -0.4 0.8]]
A biblioteca NumPy dispõe de inúmeros métodos e funções para manipular matrizes. Dito isso, leia a documentação e, se possível, não se restrinja a somente uma fonte de conteúdo. Existem diversas bibliotecas, cada uma com sua aplicação e vantagem.
Aqui e agora, espero que você tenha compreendido os conceitos de listas e matrizes em Python, bem como suas principais funcionalidades e operações. As listas são estruturas de dados extremamente versáteis, permitindo armazenar elementos de diversos tipos e tamanhos, e possuem diversas operações que facilitam sua manipulação e análise. Já as matrizes, apesar de serem estruturas mais específicas, são muito úteis para representar dados de duas ou mais dimensões, como imagens, tabelas, entre outros. Os horizontes se expandirão à medida em que novos desafios surgem.
Posted on May 12, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 27, 2024