Как мне избежать поведения Clojure в отношении ленивых последовательностей, которые я хочу замкнуть?

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

Вопрос

У меня есть длинная ленивая последовательность, которую я хочу сократить и лениво протестировать.Как только два последовательных элемента не = (или какой-либо другой предикат) друг другу, я хочу прекратить использование списка, создание которого обходится дорого.Да, это звучит как take-while, но читайте дальше.

Мне захотелось написать что-нибудь простое и элегантное вот так (представляя на минутку, что every? работает как reduce):

(every? = (range 100000000))

Но это не работает лениво и поэтому зависает на бесконечных секундах.Я обнаружил, что это работает почти так, как я хотел:

(apply = (range 100000000))

Однако я заметил, что разбиение последовательности приводило к созданию и тестированию дополнительных ненужных элементов.По крайней мере, я думаю, что именно это происходит в следующем фрагменте кода:

;; 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)))

Я нашел обходное решение, используя take-while, и count проверить количество взятых элементов, но это довольно громоздко.

Стоит ли мне вежливо предложить Ричу Хики придумать какую-нибудь комбинацию reduce и every? правильно закоротить, или я упускаю какой-то очевидный способ, который уже существует?

РЕДАКТИРОВАТЬ: Два добрых человека опубликовали решения, позволяющие избежать фрагментации ленивых последовательностей, но как мне избежать фрагментации при выполнении apply, который, похоже, потребляет порции по четыре штуки?

РЕДАКТИРОВАТЬ № 2: Как отмечает Стюарт Сьерра и я независимо друг от друга обнаружили, на самом деле это не разделение на фрагменты.Это просто нормальное поведение, поэтому я назову это закрытым и дам ему ответ.Я включил небольшую функцию в отдельный ответ, чтобы уменьшить часть проблемы для тех, кто заинтересован.

Это было полезно?

Решение

Исправлено дважды: Проще проще к неразрушающей ленивой последовательности:

(defn unchunk [s]
  (when (seq s)
    (lazy-seq
      (cons (first s)
            (unchunk (next s))))))

Первая версия опущена (when ... Итак, он вернул бесконечный SQ of Nil's после завершения последовательности ввода.

Вторая версия используется first вместо seq Так он остановился на ноль.

Re: Ваш другой вопрос, «Как мне избежать хвалить при применении, который, кажется, потребляет в ракушек в четырех четырех»:

Это связано с определением =, который, когда придавая последовательность аргументов, заставляет первые 4:

(defn =
  ;; ... other arities ...
  ([x y & more]
   (if (= x y)
     (if (next more)
       (recur y (first more) (next more))
       (= y (first more)))
     false)))

Другие советы

Глядя на определение apply в clojure.core, становится очевидным, почему оно разбивалось на группы по четыре штуки, когда apply используется с бесконечной последовательностью. Reduce тоже не замыкает... поэтому мне остается написать собственное решение:

(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)))

Затем, используя анчанк Стюарта (с дополнительным and)

(defn unchunk [s]
  (lazy-seq
   (cons (first s)
         (and (next s)
              (unchunk (next s))))))

Я получил:

(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

Если это работает и для вас, модифицируйте это.

РЕДАКТИРОВАТЬ:Модифицированная версия unchunk Стюарта после его редактирования, вероятно, предпочтительнее той, что была в этом предыдущем посте.

Я нашел этот пост, ударяя срок по времени на проблему 4Clojure, и я нашел другой способ избежать 32-х чанков:

;; add another dummy sequence parameter to the map:
(apply = (map #(do (prn %2) %) (range) (range)))

Более высокие формы карты отображаются, кажутся не использующими колоннистые последовательности (Clojure 1.5)

Вы должны сделать что-то со вторым параметром, поэтому будучи четко о том, что может быть лучше:

(apply = (map (fn [i _] (prn i) i) (range) (range)))

Это не так аккуратно, как и другие решения, но могут быть хорошими для быстрого и грязного использования, как тестирование «Это медленно из-за кусочки?».

Касательно apply, вы могли бы использовать partition Чтобы получить пары из последовательности и = их:

(every? #(apply = %) (partition 2 1
    (map (fn [i _] (prn i) i) (range) (range))))

Несмотря на то что reducep выглядит полезно тоже.

Придавать Я не хочу создавать впечатление, что разорванная последовательность медленнее, это не так. Моя проблема заключается в том, что 4Clojure Case Case вызывает «сначала» на моей функции генерирования SEQ в диапазоне значений, так что такое помещение означает, что я делаю 32 раза. (PPS. Мой код все еще слишком медленный)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top