Clojure 在列表中查找元素,并保留相邻元素
我有一个类似于Clojure 在列表中查找元素,并保留相邻元素,clojure,Clojure,我有一个类似于'(12314168901)(实际上不是数字,只是作为一个例子) 我想保留所有“1”和“1”旁边的元素 所以我想要的结果是(1 2 1 4 1 6 1) 从命令式的角度来看,我将使用for循环迭代列表,在某个索引i处找到“1”,然后将元素保持在索引i+1处 解决这个问题的一种实用的、Clojure惯用的方法是什么?使用reduce您可以沿着原始列表移动,同时创建一个新列表。还原函数f被传递到新列表以及旧列表中的下一个元素。如果到目前为止列表以1结尾,或者下一个元素是1,请将该元素添
'(12314168901)
(实际上不是数字,只是作为一个例子)
我想保留所有“1”和“1”旁边的元素
所以我想要的结果是(1 2 1 4 1 6 1)
从命令式的角度来看,我将使用for循环迭代列表,在某个索引i处找到“1”,然后将元素保持在索引i+1处
解决这个问题的一种实用的、Clojure惯用的方法是什么?使用
reduce
您可以沿着原始列表移动,同时创建一个新列表。还原函数f
被传递到新列表以及旧列表中的下一个元素。如果到目前为止列表以1
结尾,或者下一个元素是1
,请将该元素添加到新列表中。否则,保持新列表不变并继续
user> (def xs [1 2 3 1 4 1 1 6 8 9 0 1])
#'user/xs
user> (defn f [x y] (if (or (= 1 y) (= 1 (peek x))) (conj x y) x))
#'user/f
user> (reduce f [] xs)
[1 2 1 4 1 1 6 1]
我想我更喜欢这样的东西
reduce
,但这里有另一种“功能性”的观点:
您有一个值序列,该值序列应该基于某个谓词(即筛选)生成一个可能更小的值序列,并且该谓词需要向前看/向后看行为
map
的一个不太常见的用法是一次映射多个序列,例如(map f coll1 coll2 coll3)
。如果传入同一集合的“偏移”版本,则可以将其用于超前/落后逻辑
(defn my-pairs [coll]
(mapcat
(fn [prev curr]
(when (or (= 1 prev) (= 1 curr))
[curr]))
(cons ::none coll) ;; these values are used for look-behind
coll))
这是(ab)使用
mapcat
行为将映射/过滤组合成一个步骤,但也可以用map
+filter
来表达,这是另一个由于错过最后一个步骤而不起作用的想法:
(def v [1 2 3 1 4 1 1 6 8 9 0 1])
(mapcat (fn [a b] (when (= a 1) [a b])) v (rest v))
;; => (1 2 1 4 1 1 1 6 1)
所以在向量上使用两个算术版本,向量向右移动一个
您可以显式检查最后一个1并添加,然后得到一个不太优雅的工作版本:
(concat
(mapcat (fn [a b] (when (= a 1) [a b])) v (rest v))
(when (= (peek v) 1) [1]))
;; => (1 2 1 4 1 1 1 6 1)
当你想不出任何关于序列组合的聪明方法时,就手工编写递归。它不是很优雅,但很懒:
(defn keep-pairs [pred coll]
(lazy-seq
(if (empty? coll)
[]
(let [x (first coll)
xs (next coll)]
(if (pred x)
(cons x (when xs
(let [y (first xs)]
(concat (when-not (pred y) [y])
(keep-pairs pred xs)))))
(when xs
(keep-pairs pred xs)))))))
user> (keep-pairs #{1} [1 2 3 1 4 1 1 6 8 9 0 1])
(1 2 1 4 1 1 6 1)
user> (take 10 (keep-pairs #{1} (cycle [1 2 3])))
(1 2 1 2 1 2 1 2 1 2)
以下是clojure的seq processors组合的又一个解决方案:
(defn process [pred data]
(->> data
(partition-by pred)
(partition-all 2 1)
(filter (comp pred ffirst))
(mapcat #(concat (first %) (take 1 (second %))))))
user> (process #{1} [1 2 1 1 3 4 1 5 1])
;;=> (1 2 1 1 3 1 5 1)
user> (process #{1} [0 1 2 1 1 1 3 4 1 5 1 6])
;;=> (1 2 1 1 1 3 1 5 1 6)
当您需要循环数据并保留状态时,我认为一个简单的老
循环/重现是最简单的技术:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(defn keep-pairs
[data]
(loop [result []
prev nil
remaining data]
(if (empty? remaining)
result
(let [curr (first remaining)
keep-curr (or (= 1 curr)
(= 1 prev))
result-next (if keep-curr
(conj result curr)
result)
prev-next curr
remaining-next (rest remaining)]
(recur result-next prev-next remaining-next)))))
(dotest
(let [data [1 2 3 1 4 1 1 6 8 9 0 1]]
(is= [1 2 1 4 1 1 6 1]
(keep-pairs data))))
只使用cons
first
last
但是last
列表映射过滤器=
和defn
和def
的功能解决方案我不太喜欢这个解决方案。当一个好的解决方案是懒惰的时候,它是渴望的。不要在向量上使用last
→ 偶然的二次性能。改为使用peek
。@glts:last
在向量上具有线性性能,peek
具有常数。谢谢@glts,我不知道last vs.peek!更新了。@peterpun是的,这正是gits的观点last
需要线性时间,因此如果你称之为线性次数,你将获得二次性能。这比使用reduce
要好得多。我喜欢这种方法。为了避免对pred
进行多次重复检查,以及使用partition all
创建太多对(其中一半将被过滤掉),我们可以这样写:(defn process[pred data](>>data(drop while(complement pred))(按pred进行分区)(所有分区2)(mapcat#(concat(first%)(取1(第二个%)))
但是,这仍然会重复一次检查,并使用丑陋的nil
-hack作为可能不存在的最后一个(第二个%)
。为了避免这些问题,我们可以编写自己的partitionby
变体,它的输出将以一个可能为空的false
序列开始和结束,生成值,并修改process
在partitionby
步骤中删除drop,并在partitionby
步骤后添加rest
。另外,take
的第一个参数可以设置为process
的参数。我希望所有这些挥手都有意义…看。
(defn windowed-pred [n pred]
(let [window (atom [])]
(fn [rf]
(fn ([] (rf))
([acc] (rf acc))
([acc v]
(let [keep? (or (pred v) (some pred @window))]
(swap! window #(vec (take-last n (conj %1 %2))) v)
(if keep?
(rf acc v)
acc)))))))
(let [c [1 2 3 1 4 1 1 6 8 9 0 1]
pred #(= % 1)]
(eduction (windowed-pred 1 pred) c))
(defn last-or-first? [obj pair] (or (= obj (last pair)) (= obj (first pair))))
; to test, whether previous element or element is object
(defn back-shift [l] (cons nil (butlast l))) ;; back-shifts a list
(defn keep-with-follower
[obj l]
(map #'last ; take only the element itself without its previous element
(filter #(last-or-first? obj %) ; is element or previous element the object?
(map #'list (back-shift l) l)))) ; group previous element and element in list
(def l '(1 2 3 1 4 1 1 6 8 9 0 1))
(keep-with-follower 1 l)
;; => (1 2 1 4 1 1 6 1)