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)