Разделение в clojure с ленивым набором строк - PullRequest
5 голосов
/ 28 июля 2010

Начиная с набора строк, например:

(def str-coll ["abcd" "efgh" "jklm"])

Цель состоит в том, чтобы извлечь определенное количество символов из заголовка набора строк, создавая секционированную группу строк.Это желаемое поведение:

(use '[clojure.contrib.str-utils2 :only (join)])
(partition-all 3 (join "" str-coll))

((\a \b \c) (\d \e \f) (\g \h \j) (\k \l \m))

Тем не менее, при использовании объединения выполняется оценка всей коллекции, что вызывает проблемы с памятью при работе с очень большими коллекциями строк.Мой конкретный пример использования - генерация подмножеств строк из отложенной коллекции, созданной путем анализа большого файла записей с разделителями:

(defn file-coll [in-file]
  (->> (line-seq (reader in-file))
    (partition-by #(.startsWith ^String % ">"))
    (partition 2))))

и основанного на работе этого предыдущего вопроса Я пробовал сочетания Reduce, Partition и Join, но не могу придумать правильное заклинание, чтобы вытягивать символы из головы первой строки и лениво оценивать последующие строки по мере необходимости.Большое спасибо за любые идеи или указатели.

Ответы [ 2 ]

5 голосов
/ 28 июля 2010

Не совсем уверен, что вы собираетесь, но следующее делает то, что делает ваш первый пример, и делает это лениво.

Пошаговая для наглядности:

user=> (def str-coll ["abcd" "efgh" "jklm"])
#'user/str-coll
user=> (map seq str-coll)
((\a \b \c \d) (\e \f \g \h) (\j \k \l \m))
user=> (flatten *1)
(\a \b \c \d \e \f \g \h \j \k \l \m)
user=> (partition 3 *1)
((\a \b \c) (\d \e \f) (\g \h \j) (\k \l \m))

Теперь все вместе:

(->> str-coll 
  (map seq)
  flatten
  (partition 3))
1 голос
/ 29 июля 2010

РЕДАКТИРОВАТЬ: ВСЕ, ЧТО Я ПИСАЛ, НЕПРАВИЛЬНО

Когда к функции с var-arg применяется значение seq, превышающее число дискретных аргументов, остаток seq передается как var-arg (см. RestFn.applyTo ).

Юргену: Я тупой. Ты умный. Я был неправ. Ты был прав. Ты лучший. Я худший Вы очень хорошо выглядите. Я не привлекательный.

Ниже приведена запись моего идиотизма ...

<Ч />

Ответ на комментарий Юргена Хётцеля.

mapcat не является полностью ленивым, потому что apply не ленив в оценке количества применяемых аргументов. Кроме того, apply не может быть ленивым, потому что функции должны вызываться с дискретным числом аргументов. В настоящее время, если количество аргументов превышает 20, остальные аргументы сбрасываются в массив, поэтому они не ленивые.

Итак, глядя на источник для mapcat:

(defn mapcat
  "Returns the result of applying concat to the result of applying map
  to f and colls.  Thus function f should return a collection."
  {:added "1.0"}
  [f & colls]
    (apply concat (apply map f colls)))

Если мы расширим оценку, используя пример, внутренний apply будет иметь оценку:

user=> (map seq str-coll)
((\a \b \c \d) (\e \f \g \h) (\j \k \l \m))

, что хорошо, так как str-coll не полностью реализован, но тогда внешний apply оценивается как:

user=> (concat '(\a \b \c \d) '(\e \f \g \h) '(\j \k \l \m))
(\a \b \c \d \e \f \g \h \j \k \l \m)

Обратите внимание, что внешние apply применяют n аргументов к concat, по одному для каждой строки в исходном str-coll. Теперь верно, что результат concat является ленивым, и каждый аргумент сам по себе ленив, но вам все равно нужно реализовать полную длину str-coll, чтобы получить эти n ленивые последовательности. Если str-coll имеет 1000 строк, то concat получит 1000 аргументов, и все 1000 строк необходимо будет прочитать из файла и в память, прежде чем можно будет вызвать concat.

<Ч />

Для неверующих, демонстрация seq-реализующего поведения apply:

user=> (defn loud-seq [] (lazy-seq (println "HELLO") (cons 1 (loud-seq))))
#'user/loud-seq
user=> (take 3 (loud-seq)) ; displaying the lazy-seq realizes it, thus printing HELLO
(HELLO
HELLO
1 HELLO
1 1)
user=> (do (take 3 (loud-seq)) nil) ; lazy-seq not realized; no printing of HELLO
nil
user=> (do (apply concat (take 3 (loud-seq))) nil) ; draw your own conclusions
HELLO
HELLO
HELLO
nil

И демонстрация того, что варарги не ленивы:

user=> (defn foo [& more] (type more))
#'user/foo
user=> (foo 1 2 3 4)
clojure.lang.ArraySeq
user=> (apply foo (repeat 4 1))
clojure.lang.Cons

Хотя в качестве контрапункта меня смущает следующее:

user=> (take 10 (apply concat (repeat [1 2 3 4])))
(1 2 3 4 1 2 3 4 1 2)
...