Como evito o comportamento de Chunking de Clojure para seqs preguiçosos que eu quero fazer curto -circuito?

StackOverflow https://stackoverflow.com/questions/3407876

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.

Foi útil?

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)

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top