Лучший способ скопировать результаты в вектор в Clojure? (Чистый функциональный код кажется уродливым и многословным)
... Возможно, обязательное программирование с изменчивыми данными просто слишком глубоко пробурено в моем мозгу, но я нахожу код для создания векторов данных в Clojure, чтобы быть многословным, громоздким и запутанным. Должен быть лучший способ!
В Ruby я могу написать код вроде:
results = []
a_collection.each do |x|
x.nested_collection.each do |y|
next if some_condition_holds
results << y
end
end
В Clojure я не знаю лучшего способа сделать это, чем использовать рекурсивную функцию, возможно, как следующий (ужасающий) код:
; NEWBIE ALERT! NEWBIE ALERT!
(loop [results []
remaining a_collection]
(if (empty? remaining)
results
(recur
(loop [results results
nested (nested_collection (first remaining))]
(if (empty? nested)
results
(if (some_condition_holds)
(recur results (rest nested))
(recur (conj results (first nested)) (rest nested)))))
(rest remaining))))
Без изменяемых данных и итерационных циклов вам необходимо использовать рекурсию для создания коллекции. Каждая такая рекурсивная функция нуждается в предложении (empty?)
guard и т.д. И т.д. Все это так повторяется, что заставляет меня кричать.
В простых случаях map
будет достаточно, но я думаю о случаях, когда есть несколько уровней вложенности, и на каждом уровне могут быть условия, требующие пропустить итерацию.
В Common Lisp я могу использовать макрос loop
или mapcan
. Не имеет ли Clojure что-либо вроде mapcan
?
Ответы
Ответ 1
В порядке убывания того, насколько хорошо я думаю, что варианты выглядят:
(for [x coll,
y (nested-collection x)
:when (not (some-condition-holds y))]
y)
В качестве альтернативы, если вы предпочитаете использовать его из таких функций, как map
и mapcat
вместо синтаксиса for
:
(mapcat (fn [x]
(remove some-condition-holds
(nested-collection x)))
coll)
Если вы действительно заинтересованы в этом, вы также можете создать его с помощью приложений и композиций с частичными функциями:
(mapcat (comp (partial remove some-condition-holds)
nested-collection)
coll)
Этот третий стиль не очень хорошо читается в Clojure, хотя эквивалентный код на некоторых других языках очень приятный. Например, в Haskell:
coll >>= (filter (not . someConditionHolds) . nestedCollection)
Ответ 2
(mapcat (fn [y] (filter condition y)) x)
Ответ 3
Другие уже предоставили ответы о том, как решить указанную проблему, используя концепции FP, например, используя функции высокого порядка. Если вы проанализируете свой мыслительный процесс, который приведет к вашему существующему коду и сравните его с решениями FP, которые другие люди предоставили, вы обнаружите, что всякий раз, когда вы думаете о "наличии переменной для хранения обработанного результата", это приведет к обязательному OR поэтапное решение и, следовательно, ваш код Clojure в основном необходим, поскольку вы думали о сохранении результата, это "векторная переменная". Такое мышление не позволит вам применять концепции FP, основанные на "оценке выражения" и "решении проблемы по составу"
Ответ 4
функции более высокого порядка могут действительно помочь сделать его намного красивее, хотя
это займет некоторое время, чтобы привыкнуть к мышлению последовательностями и
преобразование последовательностей.
есть много способов написать это:
user> (into [] a_colletion)
[0 1 2 3 4 5 6 7 8 9]
user> (vec a_colletion)
[0 1 2 3 4 5 6 7 8 9]
user> (for [x a_colletion :when (even? x)] x)
(0 2 4 6 8)
более сложный пример может выглядеть примерно так:
(flatten (for [x (map extract-from-nested-collection a_collection)
:when (test-conditions? x)]
x))
сделать вложенную коллекцию
user> (def a_collection (map #(reductions + (range %)) (range 1 5)))
#'user/a_collection
user> a_collection
((0) (0 1) (0 1 3) (0 1 3 6))
извлекает вложенную коллекцию из каждого элемента a_collection
и пропустите некоторые из них:
user> (map #(filter pos? %) a_collection)
(() (1) (1 3) (1 3 6))
добавить вложенные коллекции вместе
user> (flatten (map #(filter pos? %) a_collection))
(1 1 3 1 3 6)
отфильтруйте что-нибудь большее, чем 3 из сплющенной коллекции, а затем соберите каждый из них
user> (for [x (flatten (map #(filter pos? %) a_collection))
:when (<= x 3)]
(* x x))
(1 1 9 1 9)
user>
Ответ 5
amalloy answer, вероятно, лучше всего, если вы хотите следовать идиоматическому функциональному стилю и создать ленивую последовательность.
Если вы действительно заинтересованы в императивном построении вектора (а не ленивой последовательности), я, вероятно, сделаю это, используя атом и дозу следующим образом:
(let [v (atom [])]
(doseq [x (range 5)
y (range 5)]
(if (> y x)
(swap! v conj (str x y))))
@v)
=> ["01" "02" "03" "04" "12" "13" "14" "23" "24" "34"]
Как вы можете видеть, это очень похоже на структуру кода Ruby.
Это также можно сделать с помощью сокращения, однако это наиболее подходит, когда есть только одна входная последовательность, например:
(reduce
(fn [v x]
(if (even? x)
(conj v x)
v))
[]
(range 20))
=> [0 2 4 6 8 10 12 14 16 18]