Dada uma lista de números inteiros, quero descobrir qual número é o mais próximo de um número que eu forneço na entrada:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
Existe alguma maneira rápida de fazer isso?
Respostas:
Se não tiver certeza de que a lista é ordenada, poderíamos usar o built-in
min()
função , para encontrar o elemento que tem a distância mínima entre o número especificado.Observe que ele também funciona com dict com chaves int, como
{1: "a", 2: "b"}
. Este método leva O (n) tempo.Se a lista já estiver classificada, ou você pode pagar o preço de classificar a matriz apenas uma vez, use o método de bissecção ilustrado na resposta de @ Lauritz, que leva apenas tempo O (log n) (observe, porém, verificar se uma lista já está classificada é O (n) e a classificação é O (n log n).)
fonte
O(n)
que um pouco de hackingbisect
fornecerá uma grande melhoriaO(log n)
(se sua matriz de entrada estiver classificada).min
, execute-a em um dicionário (items()
) em vez de em uma lista e retorne a chave em vez do valor no final.numpy.argmin
vez demin
para obter o índice em vez do valor.Renomearei a função
take_closest
para estar em conformidade com as convenções de nomenclatura do PEP8.Se você quer dizer execução rápida, em vez de gravação rápida, não
min
deve ser sua arma preferida, exceto em um caso de uso muito restrito. A solução precisa examinar todos os números da lista e fazer um cálculo para cada número. O uso é quase sempre mais rápido.min
bisect.bisect_left
O "quase" vem do fato de
bisect_left
exigir que a lista seja classificada para funcionar. Felizmente, seu caso de uso é tal que você pode classificar a lista uma vez e depois deixá-la em paz. Mesmo se não, contanto que você não precise classificar antes de cada vez que ligartake_closest
, obisect
módulo provavelmente sairá por cima. Se você estiver em dúvida, tente os dois e veja a diferença no mundo real.O Bisect trabalha repetidamente pela metade uma lista e descobre em que metade
myNumber
deve constar, olhando o valor médio. Isso significa que ele tem um tempo de execução de O (log n) em oposição ao tempo de execução de O (n) da resposta mais votada . Se compararmos os dois métodos e fornecermos ambos com uma ordenadamyList
, estes são os resultados:Portanto, neste teste em particular,
bisect
é quase 20 vezes mais rápido. Para listas mais longas, a diferença será maior.E se nivelarmos o campo de jogo removendo a pré-condição que
myList
deve ser classificada? Digamos que classifiquemos uma cópia da lista sempre quetake_closest
for chamada, deixando amin
solução inalterada. Usando a lista de 200 itens no teste acima, abisect
solução ainda é a mais rápida, embora apenas em cerca de 30%.Esse é um resultado estranho, considerando que a etapa de classificação é O (n log (n)) ! O único motivo que
min
ainda está perdendo é que a classificação é feita em código c altamente otimizado, enquantomin
é necessário chamar uma função lambda para cada item. À medida quemyList
cresce em tamanho, amin
solução será eventualmente mais rápida. Observe que tivemos que empilhar tudo a seu favor para amin
solução vencer.fonte
a=range(-1000,1000,2);random.shuffle(a)
verá quetakeClosest(sorted(a), b)
isso se tornaria mais lento.getClosest
possa ser chamado mais de uma vez para cada classificação, isso será mais rápido e, para o caso de uso de classificação única, é fácil.myList
já é um,np.array
então usarnp.searchsorted
no lugar debisect
é mais rápido.Um lambda é uma maneira especial de escrever uma função "anônima" (uma função que não tem nome). Você pode atribuir a ele qualquer nome que desejar, porque um lambda é uma expressão.
A maneira "longa" de escrever o texto acima seria:
fonte
Este código fornecerá o índice do número mais próximo de número na lista.
A solução dada pelo KennyTM é a melhor em geral, mas nos casos em que você não pode usá-la (como brython), essa função fará o trabalho
fonte
Itere a lista e compare o número mais próximo atual com
abs(currentNumber - myNumber)
:fonte
if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];
. Melhor armazenar esse valor com antecedência.É importante observar que a ideia de Lauritz de usar o bisect não encontra o valor mais próximo em MyList ao MyNumber. Em vez disso, o bisect encontra o próximo valor em ordem após MyNumber em MyList. Portanto, no caso do OP, você realmente retornaria a posição de 44 em vez da posição de 4.
Para obter o valor mais próximo de 5, você pode tentar converter a lista em uma matriz e usar argmin de numpy dessa maneira.
Eu não sei o quão rápido isso seria, meu palpite seria "não muito".
fonte
np.searchsorted
vez debisect_left
. E @Kanat é certo - a solução da Lauritz faz incluir o código que pega qual dos dois candidatos é mais perto.Expandindo a resposta de Gustavo Lima. O mesmo pode ser feito sem criar uma lista totalmente nova. Os valores na lista podem ser substituídos pelos diferenciais à medida que o
FOR
loop avança.fonte
Se eu puder adicionar à resposta de @ Lauritz
Para não ter um erro de execução, não se esqueça de adicionar uma condição antes da
bisect_left
linha:então o código completo terá a seguinte aparência:
fonte