Подтвердить что ты не робот

Лучший способ скопировать результаты в вектор в 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?

4b9b3361

Ответ 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]