
Passo 1 de 8
Definir qual tarefa deve sair primeiro
Compare a ordem de chegada com a ordem definida por prioridade e identifique a próxima tarefa disponível a cada retirada.
Trilha de aprendizado · Nível 14 · Tutorial 7
Mantenha uma coleção que permite retirar repetidamente o item de menor prioridade sem ordenar todos os elementos após cada alteração.
Definir qual tarefa deve sair primeiro
Compare a ordem de chegada com a ordem definida por prioridade e identifique a próxima tarefa disponível a cada retirada. 1 min
Entender a heap mínima dentro de uma lista
Veja como uma heap mínima organiza prioridades em uma lista sem transformar a lista em uma sequência totalmente ordenada. 2 min
Construir, inserir e retirar com heapq
Use heapify, heappush e heappop para manter uma heap mínima e observar a diferença entre consultar, retirar e percorrer sua lista interna. 3 min
Resolver empates sem comparar as tarefas
Use um contador de entrada para desempatar prioridades iguais sem exigir que as tarefas sejam comparáveis. 3 min
Alterar prioridades sem deixar a heap inválida
Atualize uma prioridade substituindo a entrada completa e reconstruindo a heap antes da próxima retirada. 2 min
Comparar os custos conforme o uso
Escolha entre heap e ordenação completa observando a mistura real de operações da sua carga. 3 min
Medir cargas equivalentes
Compare implementações equivalentes de fila de prioridades no seu computador, verificando primeiro o comportamento e depois os tempos. 3 min
Aplicar e revisar uma fila de tarefas
Integre as operações essenciais de heapq em uma fila de tarefas e verifique seu contrato de comportamento. 3 min

Passo 1 de 8
Compare a ordem de chegada com a ordem definida por prioridade e identifique a próxima tarefa disponível a cada retirada.
Em uma fila de prioridades, cada tarefa tem um número de prioridade. Neste tutorial, quanto menor o número, mais cedo a tarefa deve ser atendida.
Quando alguém pede a próxima tarefa, a coleção seleciona o menor valor de prioridade entre as tarefas disponíveis naquele momento. A ordem de chegada só seria decisiva se estivéssemos usando uma fila FIFO comum.
Observe como a tarefa urgente pode ultrapassar uma tarefa que já estava aguardando.

FIFO segue a chegada; uma fila de prioridades seleciona o menor número disponível.
Exemplo
Considere estas ações:
Na primeira retirada, as prioridades disponíveis eram 4 e 1. Na segunda, a tarefa de prioridade 1 já tinha saído, e a prioridade 2 passou a ser a menor disponível.
Portanto, não basta olhar a ordem em que todas as tarefas chegaram: é preciso considerar quais ainda estão na coleção a cada retirada.
Dica
Para descobrir a próxima saída, liste apenas as tarefas que já chegaram e ainda não foram retiradas. Entre elas, escolha a de menor prioridade numérica.
As ações ocorrem nesta ordem: chega A (prioridade 3); chega B (prioridade 1); retira; chega C (prioridade 2); retira; retira.
Coloque as tarefas na ordem em que serão retiradas.

Passo 2 de 8
Veja como uma heap mínima organiza prioridades em uma lista sem transformar a lista em uma sequência totalmente ordenada.
Uma heap mínima pode ser vista como uma árvore binária completa: os níveis são preenchidos da esquerda para a direita. Mas, em Python, ela é armazenada em uma lista comum — não são criados objetos separados para cada nó.
Exemplo de lista: [1, 4, 3, 9, 7, 8]. O valor no índice 0 representa a raiz da árvore.
Leia a lista por níveis: primeiro a raiz, depois seus filhos, depois os nós do nível seguinte.
![Diagrama relacionando a lista [1, 4, 3, 9, 7, 8] a uma árvore binária completa: 1 na raiz; 4 e 3 como filhos; 9 e 7 abaixo de 4; 8 abaixo de 3.](https://cursa-json-routes.s3.amazonaws.com/learning-paths/pt/python/b730454e0225/level-14/tutorial-07/step-02/screen-01/item-02.webp)
A posição de cada elemento na lista determina sua relação de pai e filhos.
Exemplo
Para um valor no índice i:
2i + 12i + 2Na lista [1, 4, 3, 9, 7, 8], o índice 1 contém 4. Seus filhos ficam nos índices 3 e 4: os valores 9 e 7.
Esses índices só são filhos quando existem dentro do tamanho da lista.
Em uma heap mínima de números comparáveis, nenhum pai é maior que seus filhos. Essa é a invariante que a estrutura preserva.
Como a raiz é ancestral de todos os elementos, essa regra garante que o menor valor esteja no índice 0. Porém, ela compara cada nó apenas com seus filhos; não exige comparar todos os pares da lista.
Observe que cada ligação pai-filho respeita a regra, embora os valores fora dessas ligações não estejam em ordem crescente na lista.

A heap impõe uma regra local entre pai e filhos.
Dica
A lista [1, 4, 3, 9, 7, 8] é uma heap válida, mas não está totalmente ordenada: 4 aparece antes de 3. O que importa é que 1 seja menor que 4 e 3, que 4 não seja maior que 9 e 7, e que 3 não seja maior que 8.
A lista [1, 4, 3, 9, 7, 8] pode representar uma heap mínima válida, mesmo porque 4 vem antes de 3.
Os mesmos valores podem formar heaps mínimas diferentes. Por exemplo, [1, 3, 4, 8, 7, 9] também é válida: cada pai continua menor ou igual aos próprios filhos.
Portanto, ao verificar uma heap, não exija uma ordem específica após o índice 0. Verifique as relações entre cada pai e seus filhos.
A lista [1, 6, 4, 2, 8] representa uma heap mínima válida.
Resumo

Passo 3 de 8
Use heapify, heappush e heappop para manter uma heap mínima e observar a diferença entre consultar, retirar e percorrer sua lista interna.
Importe o módulo heapq e chame heapq.heapify(valores) para transformar uma lista existente em uma heap mínima. A função modifica a lista recebida e retorna None; portanto, não faça valores = heapq.heapify(valores).
Depois da transformação, o menor valor está em valores[0]. Os demais índices representam uma heap válida, mas não uma lista totalmente ordenada.
A raiz da heap contém o menor valor, enquanto os outros valores só precisam respeitar a relação entre pai e filhos.

A posição zero guarda o menor valor; a sequência interna pode parecer desordenada.
Execute este trecho e observe o retorno de heapify.
import heapq
valores = [8, 3, 6, 1, 7]
resultado = heapq.heapify(valores)
print(resultado) # None
print(valores) # heap válida; a ordem interna não é ordenação completa
print(valores[0]) # 1Uma lista vazia ([]) já é uma heap válida. Use heappush(heap, valor) para inserir e heappop(heap) para retirar e obter o menor valor disponível. Ambas preservam a invariante da heap.
Use heap[0] apenas para consultar o menor: essa leitura não remove nada. Tanto heap[0] quanto heappop(heap) levantam IndexError se a heap estiver vazia.
Compare a consulta com a retirada no mesmo estado da heap.
import heapq
heap = []
for prioridade in [5, 2, 8, 1]:
heapq.heappush(heap, prioridade)
print(heap) # representação interna de uma heap
print(heap[0]) # consulta: mostra 1, sem remover
print(heap) # ainda contém 1
proxima = heapq.heappop(heap)
print(proxima) # 1: menor valor retirado
print(heap) # agora não contém mais 1
if heap:
print(heap[0])
else:
print("Não há prioridades pendentes.")Complete a chamada para retirar o menor valor da heap:
proxima = heapq._____(heap)
Complete a expressão que consulta o menor valor sem removê-lo:
menor = heap_____
Extrações sucessivas com heappop produzem os valores em ordem crescente. Porém, ao esvaziar a heap, seus elementos são consumidos.
Se precisar manter a heap original, crie uma cópia com copia = heap.copy() e retire da cópia. Não conclua nada pela ordem exibida na lista interna: valide o comportamento pela sequência de valores retirados.
No seu computador, salve como prioridades.py e execute com python prioridades.py. Compare a lista interna com a sequência de extrações.
import heapq
prioridades = [9, 4, 1, 7, 3]
heapq.heapify(prioridades)
print("Heap interna:", prioridades)
print("Próxima sem retirar:", prioridades[0])
print("Após consultar:", prioridades)
copia = prioridades.copy()
extraidas = []
while copia:
extraidas.append(heapq.heappop(copia))
print("Extraídas:", extraidas)
print("Cópia após extrair:", copia)
print("Heap original preservada:", prioridades)
try:
heapq.heappop(copia)
except IndexError:
print("Não é possível retirar de uma heap vazia.")Após executar o script, relate: qual foi a diferença entre consultar e retirar, qual sequência foi extraída e como ficaram a cópia e a heap original no final.
Escreva pelo menos 80 caracteres (0/80).

Passo 4 de 8
Use um contador de entrada para desempatar prioridades iguais sem exigir que as tarefas sejam comparáveis.
Uma entrada como (prioridade, tarefa) funciona enquanto as prioridades são diferentes. Mas tuplas são comparadas da esquerda para a direita: se duas prioridades empatam, Python tenta comparar as próprias tarefas para decidir a ordem.
Isso é um problema quando a tarefa é um dicionário, pois dicionários não possuem uma ordenação entre si.
Em um empate, a comparação alcança o objeto armazenado.

Com (prioridade, tarefa), prioridades iguais fazem a comparação chegar à tarefa.
Execute este exemplo em um arquivo Python ou no interpretador.
import heapq
fila = []
heapq.heappush(fila, (1, {"nome": "enviar relatório"}))
heapq.heappush(fila, (1, {"nome": "responder cliente"}))
# TypeError: '<' not supported between instances of 'dict' and 'dict'Armazene cada entrada como (prioridade, contador, tarefa). O contador é obtido de um único itertools.count() associado à fila.
Com a mesma prioridade, o menor contador vence. Assim, a heap decide o empate antes de precisar comparar a tarefa. Como o contador cresce a cada entrada, ele preserva a ordem de chegada entre tarefas de mesma prioridade.
A heap compara os campos da tupla nesta sequência.

O contador resolve o empate; a tarefa pode ser qualquer objeto, inclusive um dicionário.
Dica
heapq não adiciona estabilidade automaticamente. A ordem de chegada nos empates existe aqui porque você incluiu um contador crescente como segundo critério da tupla.
O desempacotamento ignora os critérios auxiliares quando a tarefa é retirada.
import heapq
from itertools import count
fila = []
proximo_id = count()
def adicionar(prioridade, tarefa):
entrada = (prioridade, next(proximo_id), tarefa)
heapq.heappush(fila, entrada)
adicionar(2, {"nome": "revisar orçamento"})
adicionar(1, {"nome": "responder cliente"})
adicionar(1, {"nome": "corrigir erro"})
while fila:
_, _, tarefa = heapq.heappop(fila)
print(tarefa["nome"])
# responder cliente
# corrigir erro
# revisar orçamentoExemplo
As duas primeiras tarefas retiradas têm prioridade 1, menor que 2. Entre elas, “responder cliente” entrou antes e recebeu contador menor; por isso sai antes de “corrigir erro”. Os dicionários nunca precisam ser comparados.
Complete a expressão para inserir uma tarefa com prioridade, contador e item:
entrada = (prioridade, ________, tarefa)
Execute o código da tela anterior. Explique a ordem impressa e por que não ocorre TypeError, embora as tarefas sejam dicionários.
Escreva pelo menos 60 caracteres (0/60).

Passo 5 de 8
Atualize uma prioridade substituindo a entrada completa e reconstruindo a heap antes da próxima retirada.
Uma entrada da fila tem a forma (prioridade, contador, tarefa). Se você localizar uma entrada e a substituir por outra com prioridade diferente, todos os itens continuam na lista — mas a relação entre pai e filhos pode deixar de ser válida.
Por isso, não consulte fila[0] nem chame heappop() depois da substituição até restaurar a heap com heapify(fila).
A nova prioridade 0 foi colocada em uma posição cujo pai tem prioridade 2. Isso viola a regra: um pai não pode ser maior que um filho.

Trocar uma entrada não reorganiza automaticamente os elementos da lista.
Atenção
A heap compara a prioridade armazenada na tupla, não um campo dentro do dicionário da tarefa. Mudar, por exemplo, tarefa["prioridade"] não muda o primeiro valor da tupla já presente na fila. Crie uma nova tupla com a prioridade desejada e substitua a entrada inteira.
heapq não fornece uma operação para encontrar uma tarefa arbitrária e alterar sua prioridade. Para atualizações ocasionais, faça uma varredura da lista, substitua a entrada e execute heapify.
Ao substituir, mantenha o contador original. Assim, se duas tarefas tiverem a mesma prioridade, a que entrou primeiro continua saindo primeiro.
A função recebe uma heap de entradas (prioridade, contador, tarefa) e atualiza uma tarefa identificada por "id".
import heapq
def atualizar_prioridade(fila, id_tarefa, nova_prioridade):
for indice, (prioridade, contador, tarefa) in enumerate(fila):
if tarefa["id"] == id_tarefa:
fila[indice] = (nova_prioridade, contador, tarefa)
heapq.heapify(fila)
return
raise KeyError(f"Tarefa não encontrada: {id_tarefa}")
fila = [
(1, 0, {"id": "relatorio", "titulo": "Enviar relatório"}),
(2, 1, {"id": "backup", "titulo": "Fazer backup"}),
(4, 2, {"id": "revisao", "titulo": "Revisar proposta"}),
]
heapq.heapify(fila)
atualizar_prioridade(fila, "revisao", 0)
prioridade, _, tarefa = heapq.heappop(fila)
print(prioridade, tarefa["id"])
# 0 revisaoDica
Na substituição, use (nova_prioridade, contador, tarefa), e não um contador novo. O contador existente registra a chegada original da tarefa e mantém a política de desempate estável.
Uma tarefa já presente precisa receber uma nova prioridade. Organize a estratégia simples e segura.
Considere as entradas abaixo, em que o segundo número é o contador:
fila = [(1, 0, "A"), (2, 1, "B"), (4, 2, "C")]
A entrada de C é substituída por (0, 2, "C") e então heapq.heapify(fila) é executado. Qual item será retornado pela próxima chamada a heappop(fila)?

Passo 6 de 8
Escolha entre heap e ordenação completa observando a mistura real de operações da sua carga.
Para uma lista já preenchida, heapq.heapify(lista) constrói a heap em O(n). Inserir com heappush e retirar o menor com heappop custa O(log n); consultar o menor em heap[0] custa O(1).
Esses custos assumem comparações de custo constante. O crescimento interno da lista também tem custo amortizado, como em outras listas Python.

Na heap, o menor fica acessível no topo; na lista ordenada, toda a ordem precisa continuar válida após novas entradas.
Exemplo
import heapq
valores = [8, 3, 6, 1, 7]
heapq.heapify(valores) # O(n), altera a própria lista
heap = []
for valor in [8, 3, 6, 1, 7]:
heapq.heappush(heap, valor) # n inserções: O(n log n)Se todos os valores já estão disponíveis, heapify é a construção mais adequada assintoticamente.
A estratégia simples de atualização ensinada antes — localizar uma tarefa, substituir a entrada e chamar heapify — custa O(n): a varredura é O(n) e a reconstrução também é O(n). Não é uma atualização O(log n).
Também não use uma heap esperando ordenar uma coleção inteira mais rápido: retirar seus n elementos exige n operações O(log n), totalizando O(n log n).
Uma lista em ordem decrescente permite retirar o menor pelo final com pop(), em O(1). Porém, inserir uma nova tarefa exige encontrar e abrir espaço na posição correta, com custo O(n) para manter a ordenação.
Reordenar a coleção com list.sort() pode custar até O(n log n) a cada ordenação. Na prática, o tempo também depende da ordem atual dos dados, pois a ordenação do Python é adaptável. Portanto, meça cargas equivalentes antes de concluir qual alternativa é mais rápida.
Considere comparações de custo constante e a estratégia simples de atualização por reconstrução.
Toque em um item e depois no par correspondente.
Use uma heap quando novas tarefas chegam enquanto você precisa consultar ou retirar repetidamente a próxima de menor prioridade.
Para um lote estático, sem novas entradas entre a preparação e o consumo, ordenar uma única vez pode ser mais simples e adequado. Se há muitas mudanças arbitrárias de prioridade, a atualização simples por varredura e heapify pode deixar de ser atraente.
Um sistema recebe tarefas continuamente. Após algumas inserções, ele precisa retirar a tarefa de menor prioridade; então recebe mais tarefas e repete esse ciclo. Qual estratégia combina melhor com essa carga?
Você recebe 200 mil tarefas de uma vez e, somente depois de receber todas, precisa processá-las integralmente em ordem crescente de prioridade. Não haverá inserções nem alterações durante o processamento. Qual opção é uma escolha razoável?

Passo 7 de 8
Compare implementações equivalentes de fila de prioridades no seu computador, verificando primeiro o comportamento e depois os tempos.
Para uma comparação justa, as duas soluções precisam aplicar o mesmo critério: menor prioridade primeiro e, em caso de empate, ordem de entrada.
A estratégia com heapq mantém a heap a cada inserção. A outra mantém uma lista em ordem decrescente após cada inserção; assim, seu menor item fica no final e sai com pop().
Antes de medir, confirme que ambas produzem exatamente a mesma sequência de retiradas.
As duas estruturas recebem as mesmas entradas e entregam as mesmas saídas. A diferença está no trabalho feito entre uma retirada e outra.

A heap preserva apenas a condição necessária para encontrar o menor; a lista alternativa é reordenada por inteiro a cada nova entrada.
Crie um arquivo, por exemplo comparar_heap.py, cole o código abaixo e execute python comparar_heap.py no seu computador.
As cargas são determinísticas. Cada chamada das funções cria sua própria estrutura e seu próprio count(): uma execução medida não reutiliza o estado da anterior.
O script valida a equivalência antes de imprimir o menor tempo por execução em cada cenário.
from heapq import heapify, heappop, heappush
from itertools import count
from timeit import Timer
# Dados determinísticos fora da região medida.
ENTRADAS_LOTE = [
((indice * 37) % 23 + 1, f"lote-{indice}")
for indice in range(2_000)
]
INICIAIS = [
((indice * 17) % 19 + 1, f"inicial-{indice}")
for indice in range(80)
]
INTERCALADAS = [
((indice * 29) % 19 + 1, f"nova-{indice}")
for indice in range(1_000)
]
def lote_heap(entradas):
heap = []
contador = count()
for prioridade, tarefa in entradas:
heappush(heap, (prioridade, next(contador), tarefa))
return [heappop(heap)[2] for _ in range(len(heap))]
def lote_lista_ordenada_uma_vez(entradas):
contador = count()
lista = [
(prioridade, next(contador), tarefa)
for prioridade, tarefa in entradas
]
lista.sort(reverse=True)
return [lista.pop()[2] for _ in range(len(lista))]
def intercalada_heap(iniciais, entradas):
heap = []
contador = count()
for prioridade, tarefa in iniciais:
heappush(heap, (prioridade, next(contador), tarefa))
saidas = []
for prioridade, tarefa in entradas:
heappush(heap, (prioridade, next(contador), tarefa))
saidas.append(heappop(heap)[2])
return saidas
def intercalada_lista_reordenada(iniciais, entradas):
lista = []
contador = count()
for prioridade, tarefa in iniciais:
lista.append((prioridade, next(contador), tarefa))
lista.sort(reverse=True)
saidas = []
for prioridade, tarefa in entradas:
lista.append((prioridade, next(contador), tarefa))
lista.sort(reverse=True)
saidas.append(lista.pop()[2])
return saidas
def medir(funcao, *argumentos, numero=3, repeticoes=5):
tempos = Timer(lambda: funcao(*argumentos)).repeat(
repeat=repeticoes,
number=numero,
)
return min(tempos) / numero
# Verifique comportamento antes de comparar duração.
assert lote_heap(ENTRADAS_LOTE) == lote_lista_ordenada_uma_vez(ENTRADAS_LOTE)
assert intercalada_heap(INICIAIS, INTERCALADAS) == intercalada_lista_reordenada(
INICIAIS, INTERCALADAS
)
print("Saídas equivalentes: confirmado")
print("\nLote: entradas e depois todas as retiradas")
print(f"heapq: {medir(lote_heap, ENTRADAS_LOTE):.6f} s")
print(
"lista ordenada uma vez: "
f"{medir(lote_lista_ordenada_uma_vez, ENTRADAS_LOTE):.6f} s"
)
print("\nCarga intercalada: uma inserção e uma retirada por vez")
print(f"heapq: {medir(intercalada_heap, INICIAIS, INTERCALADAS):.6f} s")
print(
"lista reordenada: "
f"{medir(intercalada_lista_reordenada, INICIAIS, INTERCALADAS):.6f} s"
)No cenário de lote, todas as entradas já existem antes da primeira retirada. A lista pode ser ordenada uma única vez, portanto essa alternativa deve entrar na comparação.
Na carga intercalada, cada inserção exige que a lista alternativa seja reordenada novamente. É justamente nesse padrão de manutenção incremental que uma heap costuma ser apropriada.
Não conclua que heapq sempre vence: os tempos dependem do tamanho, da mistura de operações, da máquina e dos custos incluídos no experimento.
Dica
Depois da primeira execução, experimente alterar apenas o tamanho das listas, por exemplo de 2_000 para 20_000. Mantenha os mesmos dados, a mesma sequência de operações e a verificação com assert. Se mudar tudo ao mesmo tempo, você não saberá o que explicou a diferença.
Execute o script. As saídas foram equivalentes? Registre os tempos que você observou para o lote e para a carga intercalada. Em seguida, diga qual estratégia pareceu mais adequada em cada carga e delimite sua conclusão às condições do teste.
Escreva pelo menos 120 caracteres (0/120).

Passo 8 de 8
Integre as operações essenciais de heapq em uma fila de tarefas e verifique seu contrato de comportamento.
Nesta aplicação, cada tarefa é um dicionário, mas a heap armazena tuplas no formato (prioridade, contador, tarefa). O contrato é observável pelas retiradas: menor prioridade numérica sai primeiro e, em caso de empate, a tarefa que entrou antes sai antes.
Após alterar uma prioridade, preserve o contador original e execute heapify. Não teste uma ordem específica da lista interna: uma heap válida só garante que o menor elemento está na posição zero.

O contador resolve empates antes que Python precise comparar os dicionários das tarefas.
Copie o código para um arquivo Python e execute-o. Os asserts verificam a ordem de atendimento, inclusive o empate entre as prioridades 1.
A atualização localiza a entrada, preserva seu contador e reconstrói a heap antes das próximas retiradas.
import heapq
from itertools import count
fila = []
contador = count()
def adicionar(prioridade, tarefa):
entrada = (prioridade, next(contador), tarefa)
heapq.heappush(fila, entrada)
def alterar_prioridade(id_tarefa, nova_prioridade):
for indice, (prioridade, ordem, tarefa) in enumerate(fila):
if tarefa["id"] == id_tarefa:
# Mantém a ordem original para desempates futuros.
fila[indice] = (nova_prioridade, ordem, tarefa)
heapq.heapify(fila)
return
raise KeyError(f"Tarefa não encontrada: {id_tarefa}")
def atender_proxima():
prioridade, ordem, tarefa = heapq.heappop(fila)
return tarefa["id"]
adicionar(2, {"id": "backup", "descricao": "Salvar dados"})
adicionar(1, {"id": "email", "descricao": "Responder cliente"})
adicionar(2, {"id": "relatorio", "descricao": "Preparar relatório"})
adicionar(1, {"id": "urgente", "descricao": "Corrigir falha"})
# "relatorio" entrou com prioridade 2, mas agora deve ser atendido primeiro.
alterar_prioridade("relatorio", 0)
ordem_atendida = []
while fila:
ordem_atendida.append(atender_proxima())
print(ordem_atendida)
assert ordem_atendida == ["relatorio", "email", "urgente", "backup"]
Depois de executar o script, registre a ordem observada. Explique por que email vem antes de urgente e por que um teste não deve comparar a lista interna da heap com uma lista totalmente ordenada.
Escreva pelo menos 80 caracteres (0/80).
Resumo
Use uma heap quando o programa recebe novas tarefas e precisa descobrir repetidamente a próxima de menor prioridade.
(prioridade, contador, tarefa) permite guardar dicionários e mantém uma política estável nos empates.heapify é uma solução simples para uma alteração ocasional de prioridade após substituir a entrada e preservar seu contador.Parabéns! Você concluiu: Gerenciar prioridades com heapq
Milhares de cursos online em vídeo, ebooks e áudiobooks.
Para testar seus conhecimentos no decorrer dos cursos online
Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail
Baixe nosso aplicativo pelo QR Code ou pelos links abaixo:.
+ de 10 milhões
de alunos
Certificado grátis e
válido em todo o Brasil
60 mil exercícios
gratuitos
4,8/5 classificação
nas lojas de apps
Cursos gratuitos em
vídeo, ebooks e audiobooks