Python: Manipulação de Listas e Matrizes

iugstav

Gustavo Soares

Posted on May 12, 2023

Python: Manipulação de Listas e Matrizes

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"]
Enter fullscreen mode Exit fullscreen mode

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, 54=205*4=20 . 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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Output:

$> python3 <nome_do_arquivo>.py
['mansão', 'thug', 'stronda']
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Output:

$> python3 <nome_do_arquivo>.py
[20, 30, 50, 40, 10]
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

Output:

$> python3 <nome_do_arquivo>.py
4
Enter fullscreen mode Exit fullscreen mode

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 valor False, portanto, é opcional;
  • key: callable é a função que especifica o critério de ordenação. Esse parâmetro recebe por padrão o valor None, 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)
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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)]
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

É possível reescrevê-lo utilizando list comprehensions na forma:

lista = [el*10 for el in range(1, 10)]
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Output:

$> python3 <nome_do_arquivo>.py
[0, 2, 4, 6, 8]
Enter fullscreen mode Exit fullscreen mode

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 nn 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]]
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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 nn 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]])
Enter fullscreen mode Exit fullscreen mode

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 nn inteiros indicando o número de elementos armazenados nas nn 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)
Enter fullscreen mode Exit fullscreen mode

Output:

$> python3 <nome_do_arquivo>.py
9
2
(3, 3)
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

Output:

$> python3 <nome_do_arquivo>.py
O produto interno é: 32
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

Output:

$> python3 <nome_do_arquivo>.py
A multiplicação resulta em:
 [[ 82 140  52]
 [ 60 109  22]
 [151 220  41]]
Enter fullscreen mode Exit fullscreen mode

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())
Enter fullscreen mode Exit fullscreen mode

Output:

$> python3 <nome_do_arquivo>.py
O traço da matriz 'a' é: 8
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

Output:

$> python3 <nome_do_arquivo>.py
O determinante de 'a' é: 5.0
Enter fullscreen mode Exit fullscreen mode

Se não tivessemos arredondado o determinante, seu resultado seria 4.9999999999999994.999999999999999 .

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)
Enter fullscreen mode Exit fullscreen mode

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]]
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
iugstav
Gustavo Soares

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