Как избежать поведения Chloking Clojure для ленивых последовательностей, которые я хочу замкнуть? - PullRequest
14 голосов
/ 04 августа 2010

У меня есть длинная ленивая последовательность, которую я хочу уменьшить и проверить лениво.Как только два последовательных элемента не равны = (или некоторому другому предикату) друг другу, я хочу прекратить использование списка, который является дорогим для производства.Да, это звучит как 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: Как отмечает Стюарт Сьерра, и я обнаружил, что на самом деле это не куски.Он просто действует как обычно, поэтому я назову это закрытым и дам ему ответ.Я включил небольшую функцию в отдельный ответ, чтобы выполнить уменьшающую часть проблемы для тех, кто заинтересован.

Ответы [ 3 ]

20 голосов
/ 05 августа 2010

ИСПРАВЛЕНО ДВАЖДЫ: Более простой способ отсоединить ленивую последовательность:

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

Первая версия опущена (when ..., поэтому она вернула бесконечное число nil после окончания последовательности ввода.

Вторая версия использовала 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)))
2 голосов
/ 15 мая 2013

Я нашел этот пост, когда нарушал ограничение по времени в 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 тоже выглядит полезным.

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

2 голосов
/ 05 августа 2010

Если посмотреть в clojure.core на определение apply, становится понятно, почему оно разбивалось на группы по четыре, когда 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 после его редактирования, вероятно, предпочтительнее, чем в этом предыдущем посте.

...