How do I filter elements from a sequence based on indexes

守給你的承諾、 提交于 2021-02-07 05:11:28

问题


I have a sequence s and a list of indexes into this sequence indexes. How do I retain only the items given via the indexes?

Simple example:

(filter-by-index '(a b c d e f g) '(0 2 3 4)) ; => (a c d e)

My usecase:

(filter-by-index '(c c# d d# e f f# g g# a a# b) '(0 2 4 5 7 9 11)) ; => (c d e f g a b)

回答1:


make a list of vectors containing the items combined with the indexes,

(def with-indexes (map #(vector %1 %2 ) ['a 'b 'c 'd 'e 'f] (range)))
#'clojure.core/with-indexes
 with-indexes
([a 0] [b 1] [c 2] [d 3] [e 4] [f 5])

filter this list

lojure.core=> (def filtered (filter #(#{1 3 5 7} (second % )) with-indexes))
#'clojure.core/filtered
clojure.core=> filtered
([b 1] [d 3] [f 5])

then remove the indexes.

clojure.core=> (map first filtered)                                          
(b d f)

then we thread it together with the "thread last" macro

(defn filter-by-index [coll idxs] 
    (->> coll
        (map #(vector %1 %2)(range)) 
        (filter #(idxs (first %)))
        (map second)))
clojure.core=> (filter-by-index ['a 'b 'c 'd 'e 'f 'g] #{2 3 1 6}) 
(b c d g)

The moral of the story is, break it into small independent parts, test them, then compose them into a working function.




回答2:


You can use keep-indexed:

(defn filter-by-index [coll idxs]
  (keep-indexed #(when ((set idxs) %1) %2) 
                coll))  

Another version using explicit recur and lazy-seq:

(defn filter-by-index [coll idxs]
  (lazy-seq
   (when-let [idx (first idxs)]
     (if (zero? idx)
       (cons (first coll)
             (filter-by-index (rest coll) (rest (map dec idxs))))
       (filter-by-index (drop idx coll)
                        (map #(- % idx) idxs))))))



回答3:


The easiest solution is to use map:

(defn filter-by-index [coll idx]
  (map (partial nth coll) idx))



回答4:


I like Jonas's answer, but neither version will work well for an infinite sequence of indices: the first tries to create an infinite set, and the latter runs into a stack overflow by layering too many unrealized lazy sequences on top of each other. To avoid both problems you have to do slightly more manual work:

(defn filter-by-index [coll idxs]
  ((fn helper [coll idxs offset]
     (lazy-seq
      (when-let [idx (first idxs)]
        (if (= idx offset)
          (cons (first coll)
                (helper (rest coll) (rest idxs) (inc offset)))
          (helper (rest coll) idxs (inc offset))))))
   coll idxs 0))

With this version, both coll and idxs can be infinite and you will still have no problems:

user> (nth (filter-by-index (range) (iterate #(+ 2 %) 0)) 1e6)
2000000

Edit: not trying to single out Jonas's answer: none of the other solutions work for infinite index sequences, which is why I felt a solution that does is needed.




回答5:


I had a similar use case and came up with another easy solution. This one expects vectors.

I've changed the function name to match other similar clojure functions.

(defn select-indices [coll indices]
   (reverse (vals (select-keys coll indices))))



回答6:


(defn filter-by-index [seq idxs]
  (let [idxs (into #{} idxs)]
    (reduce (fn [h [char idx]]
              (if (contains? idxs idx)
                (conj h char) h))
            [] (partition 2 (interleave seq (iterate inc 0))))))

(filter-by-index [\a \b \c \d \e \f \g] [0 2 3 4])
=>[\a \c \d \e]



回答7:


=> (defn filter-by-index [src indexes]
     (reduce (fn [a i] (conj a (nth src i))) [] indexes))

=> (filter-by-index '(a b c d e f g) '(0 2 3 4))
[a c d e]



回答8:


I know this is not what was asked, but after reading these answers, I realized in my own personal use case, what I actually wanted was basically filtering by a mask.

So here was my take. Hopefully this will help someone else.

(defn filter-by-mask [coll mask]
  (filter some? (map #(if %1 %2) mask coll)))

(defn make-errors-mask [coll]
  (map #(nil? (:error %)) coll))

Usage

(let [v [{} {:error 3} {:ok 2} {:error 4 :yea 7}]
    data ["one" "two" "three" "four"]
    mask (make-errors-mask v)]
    (filter-by-mask data mask))

; ==> ("one" "three")


来源:https://stackoverflow.com/questions/7744656/how-do-i-filter-elements-from-a-sequence-based-on-indexes

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!