开发者

How do I filter elements from a sequence based on indexes

I have a sequence s and a list of indexes into th开发者_开发知识库is 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)


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


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.


The easiest solution is to use map:

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


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.


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


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


=> (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]


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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜