在clojure列表中向前填充nil值的更好方法



我编写了一个函数,用于在给定列表中正向填充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)

从我所看到的,函数的最后一个参数是未使用的。考虑到这一点,以下内容应该有效。如果我对参数有什么误解,请告诉我。

(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))))))

使用reductions:

(defn fwd-fill [x xs]
(rest
(reductions 
(fn [a x] (or x a))
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)

您可以使用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")

你可以(ab(使用拉链达到同样的效果:

(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"))

这是我的版本,类似于上面的其他版本:

(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,因为列表不需要是有限的

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

最新更新