#clojure
#clojure
Вопрос:
Я изучаю Clojure и в качестве упражнения хотел написать что-то вроде команды unix «comm».
Для этого я считываю содержимое каждого файла в набор, затем использую разницу / пересечение, чтобы показать эксклюзивные / общие файлы.
После долгого времени воспроизведения я придумал что-то вроде этого для части создания набора:
(def contents (ref #{}))
(doseq [line (read-lines "/tmp/a.txt")]
(dosync (ref-set contents (conj @contents line))))
(Я использую duck-streams / read-lines для сортировки содержимого файла).
Это мой первый опыт в любом виде функционального программирования или lisp / Clojure. Например, я не мог понять, почему, когда я выполнял conj для набора, набор все еще был пуст. Это привело меня к изучению ссылок.
- Есть ли лучший Clojure / функциональный способ сделать это? Используя ref-set, я просто искажаю код для нефункционального мышления или мой код соответствует тому, как это должно быть сделано?
- Есть ли библиотека, которая уже делает это? Это кажется относительно обычным делом, но я не смог найти ничего подобного.
Комментарии:
1. Ответ Брайана Карпера хорош. Избегайте использования утиных потоков. Он устарел, большая часть его функциональности сведена к
clojure.core
иclojure.java.io
.2. О вашем комментарии о том, что набор «все еще был пуст»; похоже, вы ожидаете изменяемого поведения. Помните, что в clojure типы данных неизменяемы. Создание коллекций выполняется рекурсивно, отсюда и использование
reduce
, как показано Брайаном Карпером (into
используетсяreduce
внутри).3. @DaveRay Я не знал о состоянии утиных потоков. Спасибо за информацию.
4. @AlexStoddard Да, это была именно моя ошибка. Одно дело читать о неизменяемости, совсем другое — действительно понимать это.
Ответ №1:
Clojure 1.3:
user> (require '[clojure.java [io :as io]])
nil
user> (line-seq (io/reader "foo.txt"))
("foo" "bar" "baz")
user> (into #{} (line-seq (io/reader "foo.txt")))
#{"foo" "bar" "baz"}
line-seq
дает вам ленивую последовательность, где каждый элемент в последовательности является строкой в файле.
into
выгружает все это в набор. Чтобы сделать то, что вы пытались сделать (добавить каждый элемент по одному в набор), а не doseq
и ссылки, вы могли бы сделать:
user> (reduce conj #{} (line-seq (io/reader "foo.txt")))
#{"foo" "bar" "baz"}
Обратите внимание, что Unix comm
сравнивает два отсортированных файла, что, вероятно, является более эффективным способом сравнения файлов, чем пересечение наборов.
Редактировать: Дэйв Рэй прав, чтобы избежать утечки дескрипторов открытых файлов, лучше сделать это:
user> (with-open [f (io/reader "foo.txt")]
(into #{} (line-seq f)))
#{"foo" "bar" "baz"}
Комментарии:
1. @BrianCarper Спасибо, это действительно полезно. Использование line-seq кажется более естественным. Я видел, что reduce используется только для суммирования значений в последовательности или чего-то подобного, поэтому интересно увидеть, как он используется для создания другой коллекции.
2. Вы можете использовать функцию
set
вместо(into #{} ...
Ответ №2:
Я всегда читаю с slurp
и после этого разделяю с re-seq
из-за моих потребностей.