Dicionários

E mais I/O

Em um exercício da aula passada, calculamos o saldo de uma pessoa em uma conta coletiva. Para fazermos isso, tivemos de iterar sobre todas as transações da conta, calculando o saldo com base nas transações em que a pessoa em questão esteve envolvida. Se quiséssemos saber o saldo de todas as pessoas envolvidas na conta coletiva, como faríamos? Uma forma seria:

Expense = namedtuple('Expense', ['name', 'payer', 'amount', 'beneficiaries'])

def calculate_balances(expenses):
    # collect persons
    persons = []
    for expense in expenses:
        for person in [expense.payer] + expense.beneficiaries:
            if person not in persons:
                persons.append(person)
    # now persons has all the involved people
    balances = []
    for person in persons:
        person_balance = calculate_balance(person, expenses)
        balances.append((person, person_balance))
    return balances

Nessa forma de resolver o problema, nós temos de primeiro obter a lista de pessoas para depois calcular o saldo de cada uma. Assim, nós precisamos de iterar sobre a lista de gastos/transações \(n + 1\) vezes, em que \(n\) é o número de pessoas. (Você vê o por quê disso?) Se a lista é pequena como no nosso exemplo, não há problema, mas se a lista tem milhões de elementos nossa função vai levar bem mais tempo do que o necessário, uma vez que é possível obter o saldo de todos passando pela lista uma única vez. Uma forma de fazer isso é usando dicionários, então vamos aprender como usá-los antes de voltar a esse problema.

1. Associando chaves à valores

Dicionários, assim como tuplas e listas, são uma estrutura de dados composta. Dicionários são parte fundamental de Python, com suporte especial na linguagem, incluindo uma sintaxe própria (tuplas usam parênteses, listas usam colchetes, vocês conseguem imaginar o quê dicionários usam?). Dicionários são uma estrutura de dados associativa; além de ‘guardar’ elementos, eles ficam associados a uma chave, pela qual é possível acessá-los.

Isso não é muito de diferente de uma lista, se pensarmos bem. Afinal podemos pensar numa lista como sendo uma associação de um elemento/valor a um índice (pelo qual ele é acessado). A diferença para listas então seria que o dicionário não exige que a chave/índice seja um número inteiro, apenas que seja um valor de um tipo imutável, como uma tupla ou string ou (por que não?) um número inteiro.

empty_dict = {}  # um dicionário vazio
students_per_school = {'EPGE': 200, 'EMAP': 100, 'EBAPE': 250, 'CPDOC': 50}
students_per_school['DIREITO RIO'] = 300 # assigning a new value
print(students_per_school['EBAPE']) # getting a value and printing it

O nome ‘dicionário’ provavelmente vem do fato de um dicionário associar nomes à significados, assim como um dicionário Python associa chaves à valores.

2. Iterando sobre dicionários

Podemos iterar sobre dicionários de forma semelhante à que podemos fazer em listas, mas devemos atentar para um fato: os elementos de um dicionário não possuem ordem definida, ao contrário dos elementos de uma lista. Além disso, devemos escolher se queremos iterar somente sobre as chaves, ou sobre os itens (pares de chaves e valor):

students_per_school = {'EPGE': 200, 'EMAP': 100, 'EBAPE': 250, 'CPDOC': 50, 'DIREITO RIO': 300}

# print school names
schools = []
for school in students_per_school.keys():
    schools.append(school)
print(schools)


# print schools and their respective student names
for (school, number_of_students) in students_per_school.items():
    print(school, number_of_students)

3. Funções de dicionários

Assim como no caso de listas, existem funções exclusivas para dicionários; já outras funções funcionam em outros tipos também.

# o tamanho de um dicionário é o seu números de itens
len({}) # 0
len({"Petrobras": 20.05, "VALE": 65.52, "BB": 34.67}) # 3

# podemos verificar se uma chave existe antes de tentar indexá-la
# com ‘in’
prime_ministers = {"Edmund Barton": (1901, 1903), "Ben Chifley": (1945, 1949), "Paul Keating": (1991, 1996)}
if "Harold Holt" in prime_ministers:
    prime_ministers["Harold Holt"]

# se a chave não existir, obtemos um erro:
## prime_ministers["Harold Holt"]

# alternativamente, dict.get aceita um parâmetro extra que é
# retornado caso a chave não esteja presente no dicionário
balances = {'Geralt': 114, 'Dandelion': -40, 'Ciri': -74}
balances.get('Yennefer', 0) # 0

del(balances['Dandelion']) # remove um item do dicionário
balances.clear() # remove todos itens de um dicionário

while prime_ministers: # um dicionário não-vazio é equivalente à True
    (name, (beg, end)) = prime_ministers.popitem() # remove um item do dicionário
    print(name, beg, end)

3.1. Exercício: calculando todos os saldos de transações

Estenda o exercício da aula passada, calculando o saldo de todas as pessoas envolvidas no grupo de uma vez só (fazendo um único loop sobre os gastos do grupo). Defina uma função chamada calculate_balances com um único argumento (a lista de gastos, cada gasto sendo uma tupla nomeada), e retornando um dicionário cujas chaves são os nomes dos participantes e os valores são seus respectivos saldos.

Como exemplo, se implementamos calculate_balances corretamente, valeria a igualdade no código abaixo (exceto pela questão da precisão de números representados como floats, então na verdade você vai encontrar valores como 4.500000000000001 para o saldo de Sam):

example_expenses = [Expense(name="Breakfast", payer="Sam", amount=15.90, beneficiaries=["Frodo", "Sam", "Pippin"]),
                    Expense(name="Breakfast (again)", payer="Pippin", amount=18.30, beneficiaries=["Frodo", "Sam", "Pippin"])]
calculate_balances(example_expenses) == {'Sam': 4.5, 'Frodo': -11.4, 'Pippin': 6.9}

4. Lendo e escrevendo arquivos

Para ler e escrever arquivos, precisamos da função open. Ela nos retorna um objeto que representa o arquivo, do qual podemos ler (usando read) ou para o qual podemos escrever (usando write). Veja:

myfile = open("quotes.txt", mode='w') # w vem de 'write'
# use write para escrever algo para o arquivo
myfile.write("Are you suggesting coconuts migrate?\n")
# nunca se esqueca de fechar o arquivo depois de terminar de usá-lo
myfile.close()

Como sempre que abrimos um arquivo precisamos de fechá-lo ao terminar de usá-lo, Python nos oferece uma forma de fazer isso automaticamente, usando uma sintaxe especial:

with open("quotes.txt", mode='r') as myfile: # r vem de 'read'
  # read lê todo o conteúdo do arquivo e o retorna como uma string
  print(myfile.read())

Dentro do bloco with, myfile (ou o nome de variável que for escolhido) conterá o objeto que representa o arquivo, e podemos fazer as operações de leitura/escrita nele normalmente. Ao final do bloco, o arquivo será fechado automaticamente, e myfile sairá de escopo (deixa de existir com este valor).

4.1. Lendo arquivos CSV

Ao abrir um arquivo e lê-lo com read, colocamos todo o seu conteúdo na memória imediatamente. Se o tamanho do arquivo for superior à memória do computador, problemas vão acontecer (você pode testar, se quiser…)

Felizmente, existem formas de ler arquivos aos poucos, uma delas sendo linha-a-linha. Isso é especialmente útil para arquivos de texto estruturados em formato CSV (comma-separated values) ou TSV (tab-separated values), pois cada linha correspondente a um elemento de uma tabela. Fazemos assim:

with open("quotes.txt", mode='r') as myfile:
  for line in myfile: # loops over lines in the file
    print(line)

Mas se formos abrir um arquivo CSV, ainda precisamos cuidar de separar os valores de cada coluna da tabela, e existe uma lógica complicada para isso (o valor da coluna pode conter vírgulas, que deveriam ser o separador de colunas, então precisamos colocar aspas para delimitar o valor, mas e se o valor também contiver aspas??)

Felizmente, Python vem com um leitor de CSV:

# precisamos de importar o leitor de csv da biblioteca csv
import csv

with open('cars.csv') as csvfile:
    myreader = csv.reader(csvfile, delimiter=',', quotechar='"')
    for row in myreader:
        print(row)
        print(row[0])

4.2. Exercício: Calcule balanço final de transações carregadas de um arquivo CSV

Escreva uma função calculate_balances_from_csv que lê um arquivo CSV (invocando csv.reader) listando os gastos de um grupo, e calcula o saldo de todos os envolvidos no grupo usando a função calculate_balances definida no exercício anterior.

Neste link há um arquivo que exemplifica o formato das listas de gastos em CSV. Cada linha do arquivo é uma transação, e cada transação tem quatro campos: nome, pagador, montante/valor, e os beneficiários (uma lista de nomes separados por vírgulas). Segue um outro exemplo em formato CSV:

Breakfast,Sam,15.90,"Frodo,Sam,Pippin"
Breakfast (again),Pippin,18.30,"Frodo,Sam,Pippin"

Note que o último campo de cada linha está sempre envolto por aspas, para que não se confundam as vírgulas que separam os nomes dos beneficiários de um gasto com as vírgulas que separam os campos do arquivo CSV. (No fundo você não precisa se preocupar com isso, neste caso o leitor de CSV do Python vai fazer a coisa certa por padrão).

Você vai precisar de invocar a função str.split para separar a lista de nomes dos beneficiários de um gasto, como em

str.split("Frodo,Sam,Pippin", ",")
# ou:
# "Frodo,Sam,Pippin".split(",")
Bruno Cuconato / 2024-10-22
TOP | HOME
Computação FGV/EPGE