Como posso me afastar da escola de pensamento “for-loop”?

78

Esta é uma questão bastante conceitual, mas eu esperava que pudesse obter alguns bons conselhos sobre isso. Muito da programação que faço é com matrizes ( NumPy ); Muitas vezes eu tenho que combinar itens em duas ou mais matrizes que são de tamanhos diferentes e a primeira coisa que eu vou é um for-loop ou, pior ainda, um for-loop aninhado. Eu quero evitar for-loops, tanto quanto possível, porque eles são lentos (pelo menos em Python).

Eu sei que para muitas coisas com o NumPy existem comandos pré-definidos que eu preciso pesquisar, mas você (como programadores mais experientes) tem um processo geral de pensamento que vem à mente quando você tem para iterar alguma coisa?

Por isso, muitas vezes tenho algo assim, o que é horrível e quero evitá-lo:

small_array = np.array(["one", "two"])
big_array = np.array(["one", "two", "three", "one"])

for i in range(len(small_array)):
    for p in range(len(big_array)):
        if small_array[i] == big_array[p]:
            print "This item is matched: ", small_array[i]

Eu sei que existem várias maneiras diferentes de alcançar isso em particular, mas estou interessado em um método geral de pensamento, se existir.

    
por turnip 26.08.2014 / 14:02
fonte

3 respostas

89

Esta é uma dificuldade conceitual comum quando se está aprendendo a usar NumPy efetivamente. Normalmente, o processamento de dados em Python é melhor expresso em termos de iteradores , para manter baixo o uso de memória, maximizar oportunidades de paralelismo com o sistema de E / S e fornecer reutilização e combinação de partes de algoritmos .

Mas o NumPy transforma tudo isso de dentro para fora: a melhor abordagem é expressar o algoritmo como uma sequência de operações de matriz inteira , para minimizar a quantidade de tempo gasto no interpretador lento do Python e maximizar o quantidade de tempo gasto em rotinas NumPy compiladas rapidamente.

Aqui está a abordagem geral que eu tomo:

  1. Mantenha a versão original da função (que você está certo de que está correta) para que você possa testá-la em relação às suas versões aprimoradas, tanto para correção quanto para velocidade.

  2. Trabalhe de dentro para fora: isto é, comece com o loop mais interno e veja se pode ser vetorizado; depois, quando tiver feito isso, saia de um nível e continue.

  3. Gastar muito tempo lendo a documentação do NumPy . Há muitas funções e operações lá e elas nem sempre são nomeadas de forma brilhante, então vale a pena conhecê-las. Em particular, se você se acha pensando, "se houvesse uma função que fez isso e aquilo", vale a pena gastar dez minutos procurando por ela. Geralmente está lá em algum lugar.

Não há substituto para a prática, então vou dar alguns exemplos de problemas. O objetivo de cada problema é reescrever a função para que seja totalmente vetorizado : isto é, para que seja uma seqüência de operações NumPy em matrizes inteiras, sem loops nativos de Python (no for ou while declarações, sem iteradores ou compreensões).

Problema 1

def sumproducts(x, y):
    """Return the sum of x[i] * y[j] for all pairs of indices i, j.

    >>> sumproducts(np.arange(3000), np.arange(3000))
    20236502250000

    """
    result = 0
    for i in range(len(x)):
        for j in range(len(y)):
            result += x[i] * y[j]
    return result

Problema 2

def countlower(x, y):
    """Return the number of pairs i, j such that x[i] < y[j].

    >>> countlower(np.arange(0, 200, 2), np.arange(40, 140))
    4500

    """
    result = 0
    for i in range(len(x)):
        for j in range(len(y)):
            if x[i] < y[j]:
                result += 1
    return result

Problema 3

def cleanup(x, missing=-1, value=0):
    """Return an array that's the same as x, except that where x ==
    missing, it has value instead.

    >>> cleanup(np.arange(-3, 3), value=10)
    ... # doctest: +NORMALIZE_WHITESPACE
    array([-3, -2, 10, 0, 1, 2])

    """
    result = []
    for i in range(len(x)):
        if x[i] == missing:
            result.append(value)
        else:
            result.append(x[i])
    return np.array(result)

Spoilers abaixo. Você obterá os melhores resultados se tiver sucesso antes de olhar para as minhas soluções!

Resposta 1

np.sum(x) * np.sum(y)

Resposta 2

np.sum(np.searchsorted(np.sort(x), y))

Resposta 3

np.where(x == missing, value, x)

    
por 26.08.2014 / 16:16
fonte
8

Para tornar as coisas mais rápidas, você precisa ler as estruturas de dados e usar as apropriadas.

Para tamanhos não triviais de matriz pequena e matriz grande (digamos pequeno = 100 elementos e grande = 10.000 elementos) uma maneira de fazer isso é classificar a matriz pequena, depois iterar em matriz grande e usar uma pesquisa binária para encontre elementos correspondentes na pequena matriz.

Isso tornaria a complexidade máxima de tempo, O (N log N) (e para pequenos arrays pequenos e grandes arrays grandes é mais próximo de O (N)) onde sua solução de loop aninhada é O (N ^ 2)

No entanto. quais estruturas de dados são mais eficientes depende muito do problema real.

    
por 26.08.2014 / 15:08
fonte
-3

Você pode usar um dicionário para otimizar significativamente o desempenho

Este é outro exemplo:

locations = {}
for i in range(len(airports)):
    locations[airports["abb"][i][1:-1]] = (airports["height"][i], airports["width"][i])

for i in range(len(uniqueData)):
    h, w = locations[uniqueData["dept_apt"][i]]
    uniqueData["dept_apt_height"][i] = h
    uniqueData["dept_apt_width"][i] = w
    
por 12.10.2016 / 11:48
fonte