Curto-circuito espécie
-
11-09-2019 - |
Pergunta
Eu entendo que:
head (map (2**) [1..999999])
Só vai realmente avaliar 2 ** 1, e nenhum dos restantes, mas o livro que estou lendo diz que:
head (sort somelist)
só precisa encontrar o menor item na lista, porque isso é tudo o que é usado. Como é que isso funciona? Tanto quanto eu posso dizer, isso seria impossível com os algoritmos de ordenação que eu conheço (como bolha de classificação).
A única maneira que eu posso pensar que isso iria funcionar é se o algoritmo de classificação foram para percorrer a lista inteira procurando o menor item, e em seguida, recurse na lista sem esse item. Para mim, isso soa muito lento.
É assim que as obras função de classificação, ou há outra algoritmo de classificação Eu não sei sobre o que permitiria curto-circuito como é?
Solução
Este:
só precisa encontrar o menor item na lista, porque isso é tudo o que é usado.
... realmente deve dizer que a função só precisa fazer o mínimo de trabalho que o algoritmo de ordenação requer para encontrar o menor elemento.
Por exemplo, se estamos usando quicksort como nosso algoritmo subjacente a classificação, então head . quicksort
é equivalente ao ideal seleção algoritmo conhecido como ' QuickSelect ', o que é pior caso linear. Além disso, podemos implementar k -quickselect meramente por take k . quicksort
.
notas Wikipédia em seu artigo sobre algoritmos de seleção de que (o sublinhado é meu):
Como suporte de idioma para ordenação é mais onipresente, é preferível a abordagem simplista de triagem seguido de indexação em muitos ambientes, apesar de sua desvantagem na velocidade. De fato, para línguas preguiçosos, esta abordagem simplista pode até dar-lhe o melhor complexidade possível para o k menor / maior classificadas (com máximo / mínimo como um caso especial) se o seu tipo é bastante preguiçoso.
Quicksort funciona bem neste cenário, enquanto que o padrão tipo em Haskell (merge sort) não compõe muito bem, como faz mais trabalho do que o estritamente necessário para retornar cada elemento da lista ordenada. Como este post na lista de discussão Haskell notas:
quicksort preguiçoso é capaz de produzir o lote do primeiro k menores elementos em
O (n + k log k) o tempo total de [1]
Enquanto as necessidades mergesort preguiçoso
O (n + k log n) o tempo total de [2]
Por mais que você pode gostar de ler este post .
Outras dicas
Se você criar uma função de comparação que traça seus argumentos, como este na linha de comando do GHCi:
> :module + Data.List Debug.Trace
> let myCompare x y = trace ("\tCmp " ++ show x ++ " " ++ show y) $ compare x y
então você pode ver o comportamento se:
> sortBy myCompare "foobar"
" Cmp 'f' 'o'
Cmp 'o' 'b'
Cmp 'f' 'b'
Cmp 'a' 'r'
Cmp 'b' 'a'
a Cmp 'b' 'r'
b Cmp 'f' 'o'
Cmp 'f' 'r'
f Cmp 'o' 'o'
Cmp 'o' 'r'
o Cmp 'o' 'r'
or"
Haskell está avaliando a corda preguiçosamente, um carácter de cada vez. A coluna da esquerda está sendo impresso como cada caractere é encontrado, com a gravação da coluna do lado direito as comparações necessárias, como impresso por "trace".
Note que se você compilar este, especialmente com otimizações sobre, você pode obter um resultado diferente. O otimizador executa um analisador de rigor que irá provavelmente aviso de que toda a cadeia será impresso, por isso seria mais eficiente para avaliá-lo ansiosamente.
Em seguida, tente
> head $ sortBy myCompare "foobar"
Cmp 'f' 'o'
Cmp 'o' 'b'
Cmp 'f' 'b'
Cmp 'a' 'r'
Cmp 'b' 'a'
'a'
Se você quer entender como isso funciona, procure o código-fonte para a função de classificar e avaliar 'tipo 'foobar'' manualmente em papel.
qsort [] = []
qsort (x:xs) = qsort less ++ [x] ++ qsort greater
where (less, greater) = partition (< x) xs
Assim
qsort ('f':"oobar")
= qsort ('b':"a") ++ "f" ++ qsort ('o':"or")
= ("a" ++ "b") ++ "f" ++ qsort ('o':"or")
E agora nós temos feito o suficiente para descobrir que 'a' é o primeiro item no resultado sem ter que avaliar a outra chamada para "qsort". Omiti a comparação real, porque a sua escondido dentro da chamada para "partição". Na verdade "partição" também é preguiçoso, por isso, de fato, o argumento para o outro "qsort" não foi avaliada, tanto quanto eu mostrei-lo.
O algoritmo que você acabou de descrever tem um nome específico: "tipo selecção". É de O (n 2 ) para que ele não é bem a coisa mais rápida que você poderia fazer. No entanto, se você quiser que os primeiros elementos "k" na matriz classificada, a complexidade seria O (kn) que é bom se "k" é pequeno o suficiente (como o seu exemplo).
Note que você está usando uma função pura em uma linguagem funcional. O compilador é provável que seja capaz de gerar código otimizado para sort
em ambos os casos por olhar para a forma como as funções são compostas. Ele pode facilmente inferir que você deseja que o elemento mínimo quando você head
compor e sort
.