Pular para o conteúdo

    Capítulo 20, Intermediário

    Ordenar, buscar e contar

    Ordenar um array é fácil. O que realmente se usa é a **posição** em que cada elemento ficaria, porque ela permite reordenar outros arrays na mesma ordem.

    Código deste capítulo: intermediario/cap20_ordenar_buscar.py

    `sort` e `argsort`

    O np.sort devolve uma cópia ordenada. O np.argsort devolve, em vez dos valores, as posições que ordenariam o array, e essa é a ferramenta mais versátil:

    intermediario/cap20_ordenar_buscar.pylinhas 10 a 15
    import numpy as np
    
    v = np.array([30, 10, 50, 20, 40])
    print(np.sort(v), v)
    print(np.argsort(v))
    print(v[np.argsort(v)[::-1]])
    
    Saída
    [10 20 30 40 50] [30 10 50 20 40]
    [1 3 0 4 2]
    [50 40 30 20 10]
    

    Com as posições você ordena um array pela chave de outro: classificar nomes pelas notas, por exemplo. Para a ordem decrescente, ordene o negativo:

    intermediario/cap20_ordenar_buscar.pylinhas 17 a 19
    nomes = np.array(["Ana", "Bia", "Caio"])
    notas = np.array([7.5, 9.0, 6.0])
    print(nomes[np.argsort(-notas)])
    
    Saída
    ['Bia' 'Ana' 'Caio']
    

    Em duas dimensões, o axis escolhe a direção da ordenação:

    intermediario/cap20_ordenar_buscar.pylinhas 21 a 23
    m = np.array([[3, 1, 2], [9, 8, 7]])
    print(np.sort(m, axis=1))
    print(np.sort(m, axis=0))
    
    Saída
    [[1 2 3]
     [7 8 9]]
    [[3 1 2]
     [9 8 7]]
    

    Ordenar por vários critérios

    O np.lexsort ordena por várias chaves, e a última chave da tupla é a principal. Para ordenar por sobrenome e, em caso de empate, por idade, a idade vem primeiro:

    intermediario/cap20_ordenar_buscar.pylinhas 28 a 31
    sobrenome = np.array(["Silva", "Souza", "Silva", "Souza"])
    idade = np.array([30, 25, 20, 40])
    ordem = np.lexsort((idade, sobrenome))
    print(ordem.tolist())
    
    Saída
    [2, 0, 1, 3]
    

    Buscar em um array ordenado

    O np.searchsorted encontra, por busca binária (rápida mesmo em milhões de elementos), a posição onde cada valor se encaixaria. Ele é a base para agrupar valores em faixas, e o np.digitize faz isso diretamente:

    intermediario/cap20_ordenar_buscar.pylinhas 36 a 41
    ordenado = np.array([10, 20, 30, 40])
    print(np.searchsorted(ordenado, [5, 20, 25, 99]))
    
    idades = np.array([5, 17, 18, 40, 65, 90])
    faixas = np.digitize(idades, [18, 65])
    print(faixas)
    
    Saída
    [0 1 2 4]
    [0 0 1 1 2 2]
    

    As faixas do digitize são: 0 abaixo de 18, 1 de 18 até 64 e 2 de 65 em diante.

    Valores únicos e pertencimento

    intermediario/cap20_ordenar_buscar.pylinhas 46 a 49
    a = np.array([3, 1, 2, 3, 1])
    print(np.unique(a), np.isin(a, [1, 2]))
    valores, primeira, contagem = np.unique(a, return_index=True, return_counts=True)
    print(valores, primeira, contagem)
    
    Saída
    [1 2 3] [False  True  True False  True]
    [1 2 3] [1 2 0] [2 1 2]
    

    O np.isin pergunta, para cada elemento, se ele está em um conjunto, e substitui o antigo np.in1d.

    Os k maiores, sem ordenar tudo

    Para achar os 3 maiores entre milhões de valores, ordenar tudo é desperdício. O np.argpartition coloca os k elementos desejados na frente, sem ordená-los, em tempo proporcional ao tamanho do array:

    intermediario/cap20_ordenar_buscar.pylinhas 54 a 56
    pontos = np.array([55, 91, 12, 78, 66, 99, 40])
    topo3 = np.argpartition(-pontos, 3)[:3]
    print(sorted(pontos[topo3].tolist(), reverse=True))
    
    Saída
    [99, 91, 78]
    

    Ordenações estáveis

    Se dois elementos são iguais, uma ordenação estável mantém a ordem original entre eles. Para garantir isso no argsort, passe kind="stable". Isso importa quando você ordena em etapas (primeiro por uma chave, depois por outra).

    Exercício 1

    Ranking dos melhores

    Escreva ranking(nomes, notas, k) que devolva os nomes dos k melhores, do melhor para o pior.