Как добавить к закрывающему vec, пока не будет выполнено определенное условие, относящееся к накопленному vec? - PullRequest
0 голосов
/ 25 сентября 2019

Для моего минимального примера я хотел бы добавить карты в вектор, пока сумма одного из полей карты не достигнет определенного предела.

Это моя попытка.Очевидно, что неправильно, потому что items не изменяется внутри цикла while.

(let [items []]
    (into items (while (< (reduce + (map :count items)) 100)
              {:something "x"
               :count     (+ 1 (rand-int 25))})))

Ответы [ 2 ]

0 голосов
/ 25 сентября 2019

Для промежуточного итога, reductions - хороший ответ.

Если у вас есть более общий предикат для рассмотрения, пожалуйста, посмотрите на функцию take-while-result в библиотека Tupelo :

(s/defn take-while-result
  "Takes from a collection based on a predicate with a collection argument.
  Continues taking from the source collection until `(pred <taken-items>)` is falsey.
  If pred is never falsey, `coll` is returned."
  [pred :- s/Any ; a predicate function  taking a list arg
   coll :- tsk/List]
  (when (empty? coll)
    (throw (ex-info "items must not be empty" {:coll coll})))
  (let [all-vals (vec coll)
        num-vals (count all-vals)]
    (loop [i      1
           result []] ; start by taking first value
      (if (< num-vals i)
        result
        (let [test-vals (subvec all-vals 0 i)]
          (if (not (pred test-vals))
            result
            (recur (inc i) test-vals)))))))

и модульные тесты:

(dotest
  (let [items     [{:name :a :count 1}
                   {:name :b :count 2}
                   {:name :c :count 3}
                   {:name :d :count 4}
                   {:name :e :count 5}]
        sum-count (fn sum-count-fn [items] (reduce + (map :count items)))]
    (throws? (t/take-while-result #(<= (sum-count %) 0) []))

    (is= (t/take-while-result #(<= (sum-count %) -1) items) [])
    (is= (t/take-while-result #(<= (sum-count %) 0) items) [])
    (is= (t/take-while-result #(<= (sum-count %) 1) items)
      [{:name :a, :count 1}])
    (is= (t/take-while-result #(<= (sum-count %) 2) items)
      [{:name :a, :count 1}])
    (is= (t/take-while-result #(<= (sum-count %) 3) items)
      [{:name :a, :count 1} {:name :b, :count 2}])
    (is= (t/take-while-result #(<= (sum-count %) 4) items)
      [{:name :a, :count 1} {:name :b, :count 2}])
    (is= (t/take-while-result #(<= (sum-count %) 5) items)
      [{:name :a, :count 1} {:name :b, :count 2}])
    (is= (t/take-while-result #(<= (sum-count %) 6) items)
      [{:name :a, :count 1} {:name :b, :count 2} {:name :c, :count 3}])
    (is= (t/take-while-result #(<= (sum-count %) 7) items)
      [{:name :a, :count 1} {:name :b, :count 2} {:name :c, :count 3}])
    (is= (t/take-while-result #(<= (sum-count %) 8) items)
      [{:name :a, :count 1} {:name :b, :count 2} {:name :c, :count 3}])
    (is= (t/take-while-result #(<= (sum-count %) 9) items)
      [{:name :a, :count 1} {:name :b, :count 2} {:name :c, :count 3}])
    (is= (t/take-while-result #(<= (sum-count %) 10) items)
      [{:name :a, :count 1} {:name :b, :count 2} {:name :c, :count 3} {:name :d, :count 4}])
    (is= (t/take-while-result #(<= (sum-count %) 11) items)
      [{:name :a, :count 1} {:name :b, :count 2} {:name :c, :count 3} {:name :d, :count 4}])
    (is= (t/take-while-result #(<= (sum-count %) 14) items)
      [{:name :a, :count 1} {:name :b, :count 2} {:name :c, :count 3} {:name :d, :count 4}])
    (is= (t/take-while-result #(<= (sum-count %) 15) items)
      [{:name :a, :count 1} {:name :b, :count 2} {:name :c, :count 3} {:name :d, :count 4} {:name :e, :count 5}])
    (is= (t/take-while-result #(<= (sum-count %) 16) items)
      [{:name :a, :count 1} {:name :b, :count 2} {:name :c, :count 3} {:name :d, :count 4} {:name :e, :count 5}])))

Вам также могут быть интересны связанные функции:

  • (index-using pred coll)
  • (split-using pred coll)
  • (partition-using pred values)
  • (split-match coll tgt)
0 голосов
/ 25 сентября 2019

Вы можете сделать это с помощью reduce, сохраняя текущую сумму :count:

(defn random-map []
  {:something "x"
   :count     (inc (rand-int 25))})

(reduce
  (fn [[out sum] {:keys [count] :as m}]
    (let [new-sum (+ sum count)]
      (if (< new-sum 100)
        [(conj out m) (+ sum count)]
        (reduced out))))
  [[] 0] ;; initial accumulator: empty output and sum is 0
  (repeatedly random-map))
=>
[{:something "x", :count 7}
 {:something "x", :count 4}
 {:something "x", :count 14}
 {:something "x", :count 10}
 {:something "x", :count 22}
 {:something "x", :count 4}
 {:something "x", :count 20}
 {:something "x", :count 17}]
(apply + (map :count *1))
=> 98

Вот еще один способ сделать это с reductions:

(as-> (repeatedly random-map) maps
      (map vector maps (reductions + (map :count maps)))
      (take-while #(< (second %) 100) maps)
      (map first maps))

Другая фраза, с которой, возможно, будет немного легче следовать / работать:

(as-> (repeatedly random-map) maps
      (map #(assoc %1 :running-sum %2) maps (reductions + (map :count maps)))
      (take-while #(< (:running-sum %) 100) maps))

Версия reductions является более краткой, но требует немного больше знаний о множественности map и может быть менее эффективной в некоторыхслучаи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...