在Clojure列表中转发填充零值的更好方法

问题描述 投票:2回答:4

我编写了一个函数来转发给定列表中的nil值。该功能按预期工作,但是我想知道是否有更好的方法(请阅读更多习惯用法)在clojure中实现此功能。

通过向前填充nil值,我的意思是:将最后一个非nil元素向前传播到下一个非nil元素。

功能

(defn ffill [mylist first-value last-value]
  (let [mylist-0 (concat (list first-value) mylist)
        mylist-N (concat mylist-0 (list (dec (count mylist-0))))
        mylist-idx (map-indexed (fn [i val] (if (not (nil? val)) i nil)) mylist-N)
        mylist-idx-no-nils (filter #(->> % (nil?) (not)) mylist-idx)
        ffidx (flatten (map #(repeat (- %2 %1) %1) mylist-idx-no-nils (next mylist-idx-no-nils)))]
    (map #(nth mylist-N %) (next ffidx))
     ))

示例

(ffill '(nil nil nil "a" "b" "c" nil "d" nil) "a" "d")
("a" "a" "a" "a" "b" "c" "c" "d" "d")

(ffill '("z" nil nil "a" "b" "c" nil "d" nil) "a" "d")
("z" "z" "z" "a" "b" "c" "c" "d" "d")

(ffill '("z" nil nil "a" "b" "c" nil "e") "a" "d")
("z" "z" "z" "a" "b" "c" "c" "e")

(ffill '(0 nil nil nil 4 5 nil nil 8 nil) 0 8)
(0 0 0 0 4 5 5 5 8 8)

(ffill '(0 nil nil nil 4 5 nil nil 8) 0 8)
(0 0 0 0 4 5 5 5 8)

(ffill '(nil nil nil nil 4 5 nil nil 8 nil) 0 8)
(0 0 0 0 4 5 5 5 8 8)
clojure
4个回答
2
投票

您可以使用lazy-seq

(defn left-fill-lazy [init coll]
  (when (seq coll)
    (lazy-seq
      (let [v (first coll)
            n (if (some? v) v init)]
        (cons n (left-fill-lazy n (rest coll)))))))

(left-fill-lazy "a" '(nil nil "a" nil "c" nil "d" nil))
=> ("a" "a" "a" "a" "c" "c" "d" "d")

您可以使用跟踪最近的非零值的传感器:

(defn left-fill
  ([] (left-fill nil))
  ([init]
   (fn [rf]
     (let [p (volatile! init)]
       (fn
         ([] (rf))
         ([result] (rf result))
         ([result input]
          (if (some? input)
            (do (vreset! p input)
                (rf result input))
            (rf result @p))))))))

(sequence (left-fill "a") '(nil nil "a" nil "c" nil "d" nil))
=> ("a" "a" "a" "a" "c" "c" "d" "d")

您可以(使用)拉链以达到相同的效果:

(defn left-fill-zip [l]
  (loop [loc (z/seq-zip l)]
    (if (z/end? loc)
      (z/root loc)
      (recur
        (z/next
          (cond
            (some? (z/node loc)) loc
            (z/left loc) (z/replace loc (z/node (z/left loc)))
            :else loc))))))

(left-fill-zip '("z" nil nil ("a" "b" ("c" nil) "d" nil)))
=> ("z" "z" "z" ("a" "b" ("c" "c") "d" "d"))

2
投票

据我所知,函数的最后一个参数尚未使用。鉴于此,以下应该起作用。如果我对参数有误解,请告诉我。

(defn propagate-non-nil-values [s begin]
  (when (seq s)
    (if (nil? (first s))
      (cons begin (propagate-non-nil-values (rest s) begin))
      (cons (first s) (propagate-non-nil-values (rest s) (first s))))))

2
投票

使用reductions

(defn fwd-fill [x xs]
 (rest
  (map first
   (reductions 
     (fn [[s p] x] [(or x p s) x])
     [nil x]
     xs))))  

(fwd-fill 0 '(nil nil nil nil 4 5 nil nil 8 nil))
;==> (0 0 0 0 4 5 5 5 8 8)

0
投票

这是我的版本,与上面的版本相似:

(defn ffill [xs default]
  (when (seq xs)
    (let [x           (first xs)
          new-default (if (nil? x) default x)]
      (cons new-default
            (lazy-seq (ffill (rest xs) new-default))))))

;; (ffill '(nil nil nil "a" "b" "c" nil "d" nil) "a")
;; => ("a" "a" "a" "a" "b" "c" "c" "d" "d")

;; (ffill '("z" nil nil "a" "b" "c" nil "d" nil) "a")
;; => ("z" "z" "z" "a" "b" "c" "c" "d" "d")

;; (ffill '("z" nil nil "a" "b" "c" nil "e") "a")
;; => ("z" "z" "z" "a" "b" "c" "c" "e")

;; (ffill '(0 nil nil nil 4 5 nil nil 8 nil) 0)
;; => (0 0 0 0 4 5 5 5 8 8)

;; (ffill '(0 nil nil nil 4 5 nil nil 8) 0)
;; => (0 0 0 0 4 5 5 5 8)

;; (ffill '(nil nil nil nil 4 5 nil nil 8 nil) 0)
;; => (0 0 0 0 4 5 5 5 8 8)

;; (ffill '(nil true nil false nil) 1)
;; => (1 true true false false)
  • [when处理xs的序列的结尾
  • [new-default被确定为列表的当前部分的开头或为此迭代提供的默认值
  • [lazy-seq,因为列表不必是有限的]

编辑:我先前的解决方案使用or,这是不正确的,因为它将替换false值,就像它们是nil,所以我更新了答案。

© www.soinside.com 2019 - 2024. All rights reserved.