有什么更好,更惯用的方式从seq采取“虽然不够”?

时间:2013-04-23 18:22:29

标签: clojure

我需要根据一些数量规则从序列中获取一些元素。这是我提出的解决方案:

(defn take-while-not-enough
[p len xs]
(loop [ac 0
       r []
       s xs]
       (if (empty? s)
            r
            (let [new-ac (p ac (first s))]
                (if (>= new-ac len)
                    r
                    (recur new-ac (conj r (first s)) (rest s)))))))

(take-while-not-enough + 10 [2 5 7 8 2 1]) ; [2 5]

(take-while-not-enough #(+ %1 (%2 1)) 7 [[2 5] [7 8] [2 1]]) ; [[2 5]]

有没有更好的方法来实现同样的目标?

感谢。

更新

有人发布了该解决方案,但之后将其删除了。它同样是我接受的答案,但更具可读性。谢谢你,匿名的祝福者!

(defn take-while-not-enough [reducer-fn limit data] 
   (->> (reductions reducer-fn 0 data)      ; 1. the sequence of accumulated values
        (map vector data)                   ; 2. paired with the original sequence
        (take-while #(< (second %) limit))  ; 3. until a certain accumulated value
        (map first)))                       ; 4. then extract the original values

4 个答案:

答案 0 :(得分:2)

我的第一个想法是将此问题视为减少的变体,从而将问题分解为两个步骤:

  • 计算结果中的项目数
  • 从输入中获取许多内容

我还对参数名称采取了一些自由:

user> (defn take-while-not-enough [reducer-fn limit data] 
       (take (dec (count (take-while #(< % limit) (reductions reducer-fn 0 data)))) 
             data)) 
#'user/take-while-not-enough 

user> (take-while-not-enough #(+ %1 (%2 1)) 7 [[2 5] [7 8] [2 1]])    
([2 5])     

user> (take-while-not-enough + 10 [2 5 7 8 2 1]) 
(2 5)  

这将返回一个序列,您的示例会返回一个向量,如果这很重要,那么您可以添加对vec的调用

答案 1 :(得分:1)

只能遍历输入序列一次的东西:

(defn take-while-not-enough [r v data]
  (->> (rest (reductions (fn [s i] [(r (s 0) i) i]) [0 []] data))
       (take-while (comp #(< % v) first))
       (map second)))

答案 2 :(得分:0)

好吧,如果你想使用平地/有用,这是一种使用glue的好方法:

(defn take-while-not-enough [p len xs]                                                              
  (first (glue conj []                                                                              
               (constantly true)                                                                    
               #(>= (reduce p 0 %) len)                                                             
               xs)))

但是每当它决定是否增加块数时,它就会为整个“处理过的”块重建累加器,所以它是O(n ^ 2),这对于更大的输入来说是不可接受的。

对您的实现最明显的改进是使其变得懒惰而不是尾递归:

(defn take-while-not-enough [p len xs]                                                                                    
  ((fn step [acc coll]                                                                          
     (lazy-seq                                                                                  
       (when-let [xs (seq coll)]                                                                  
         (let [x (first xs)                                                                     
               acc (p acc x)]                                                                   
           (when-not (>= acc len)                                                               
             (cons x (step acc xs)))))))                                                        
   0 xs))

答案 3 :(得分:0)

有时lazy-seq是直截了当且自我解释的。

(defn take-while-not-enough
  ([f limit coll] (take-while-not-enough f limit (f) coll))
  ([f limit acc coll]
   (lazy-seq
     (when-let [s (seq coll)]
       (let [fst  (first s)
             nacc (f acc fst)]
         (when (< nxt-sd limit)
           (cons fst (take-while-not-enough f limit nacc (rest s)))))))))

注意:f应遵循reduce规则。