Como evito o comportamento de Chunking de Clojure para seqs preguiçosos que eu quero fazer curto -circuito?
-
25-09-2019 - |
Pergunta
Eu tenho uma sequência longa e preguiçosa que quero reduzir e testar preguiçosamente. Assim que dois elementos seqüenciais não são =
(ou algum outro predicado) entre si, quero parar de consumir a lista, o que é caro de produzir. Sim, isso soa como take-while
, mas leia mais.
Eu queria escrever algo simples e elegante assim (fingindo por um minuto que every?
funciona como reduce
):
(every? = (range 100000000))
Mas isso não funciona preguiçosamente e, portanto, fica em seqs infinitos. Eu descobri que isso funciona quase como eu queria:
(apply = (range 100000000))
No entanto, notei que o chunking de sequência estava resultando em elementos extras e desnecessários sendo criados e testados. Pelo menos, é isso que eu acho que é isso que está acontecendo no seguinte bit de código:
;; Displays chunking behavior in groups of four on my system and prints 1 2 3 4
(apply = (map #(do (println %) %) (iterate inc 1)))
;; This prints 0 to 31
(apply = (map #(do (println %) %) (range)))
Eu encontrei uma solução alternativa usando take-while
, e count
Para verificar o número de elementos tirados, mas isso é bastante pesado.
Devo sugerir educadamente a rica hickey que ele faça uma combinação de reduce
e every?
Curto -circuito corretamente, ou estou perdendo uma maneira óbvia que já existe?
EDITAR: Duas pessoas gentis postaram soluções para evitar o Chunking nas sequências preguiçosas, mas como evito Chunking ao fazer o apply
, o que parece estar consumindo em grupos de quatro?
Editar #2: Como observa Stuart Sierra e eu descobri de forma independente, isso não está realmente chunking. É apenas aplicar a atuação normalmente, então vou chamar isso de fechamento e dar a ele a resposta. Incluí uma pequena função em uma resposta separada para fazer a parte reduzida do problema, para aqueles que estão interessados.
Solução
Corrigido duas vezes: Uma maneira mais simples de despachar uma sequência preguiçosa:
(defn unchunk [s]
(when (seq s)
(lazy-seq
(cons (first s)
(unchunk (next s))))))
Primeira versão omitida (when ...
Por isso, retornou um SEQ infinito de NIL após o término da sequência de entrada.
Segunda versão usada first
ao invés de seq
Então parou em nil.
RÉ: sua outra pergunta, "Como evito Chunking ao fazer a aplicação, o que parece estar consumindo em grupos de quatro":
Isto é devido à definição de =
, que, quando recebeu uma sequência de argumentos, força os 4 primeiros:
(defn =
;; ... other arities ...
([x y & more]
(if (= x y)
(if (next more)
(recur y (first more) (next more))
(= y (first more)))
false)))
Outras dicas
Olhar em clojure.core na definição de aplicação torna óbvio por que estava rolando em grupos de quatro quando apply
é usado com uma sequência infinita. Reduce
Também não é o curto -circuito ... então estou escrevendo minha própria solução:
(defn reducep
"Like reduce, but for use with a predicate. Short-circuits on first false."
([p coll]
(if-let [s (seq coll)]
(reducep p (first s) (next s))
(p)))
([p val coll]
(if-let [s (seq coll)]
(if-let [v (p val (first s))]
(recur p (first s) (next s))
false)
true)))
Em seguida, usando o Stuart's Uncomunk (com um extra and
)
(defn unchunk [s]
(lazy-seq
(cons (first s)
(and (next s)
(unchunk (next s))))))
Eu recebo:
(reducep = (map #(do (print %) %) (unchunk (range)))) ;; Prints 01, returns false
(reducep = (map #(do (print %) %) (repeat 20 1))) ;; returns true
(reducep = (map #(do (print %) %) (unchunk [0 0 2 4 5]))) ;; Returns false
(reducep = (map #(do (print %) %) (unchunk [2 2 2 2 2]))) ;; returns true
Se isso funcionar para você também, modere isso.
EDITAR: A versão modificada de Stuart de Unchunk depois que sua edição provavelmente é preferível àquela neste post anterior.
Encontrei este post enquanto atingia um limite de tempo em um problema de 4clojure e encontrei outra maneira de evitar os 32 chunks:
;; add another dummy sequence parameter to the map:
(apply = (map #(do (prn %2) %) (range) (range)))
As formas mais altas de mapa não parecem usar sequências de grama (Clojure 1.5)
Você tem que fazer algo com o segundo parâmetro, portanto, ser explícito sobre isso pode ser melhor:
(apply = (map (fn [i _] (prn i) i) (range) (range)))
Isso não é tão elegante quanto as outras soluções, mas pode ser bom para usos rápidos e sujos, como testes "Isso é lento por causa do ritmo?".
Em relação a apply
, você poderia usar partition
Para obter pares da sequência e = eles:
(every? #(apply = %) (partition 2 1
(map (fn [i _] (prn i) i) (range) (range))))
Embora reducep
parece útil também.
Ps. Não quero dar a impressão de que a sequência robusta é mais lenta, não é. Meu problema é que o caso de teste de 4clojure chama "primeiro" na minha função de geração de SEQ em vários valores, então Chunking significa que eu faço 32 vezes o trabalho. (PPS. Meu código ainda está muito lento)