Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

Drop every nth element from a sequence

The problem is simple: write a function that takes a number n and a sequence. The function returns a new sequence with every nth element removed.

For example:

(drop-every 3 [:a :b :c :d :e :f :g])
;=> (:a :b :d :e :g)

Bonus points for laziness, efficiency, and concision.

https://purelyfunctional.tv/issues/purelyfunctional-tv-newsletter-316-avoid-licensing-and-support-issues-with-the-right-jdk-for-you/

Note: These solutions are untested. Testing them is left as an exercise for you.

(defn drop-every
[n coll]
(flatten (partition-all (dec n) n coll)))
(drop-every 2 (range 1 13)) => (1 3 5 7 9 11)
(drop-every 3 (range 1 13)) => (1 2 4 5 7 8 10 11)
(drop-every 5 (range 1 13)) => (1 2 3 4 6 7 8 9 11 12)

PF.tv challenge

Let’s first write a function to check the outputs. Since it’s quite simple, I’ll use the example from the problem definition and a couple of corner cases:

(defn spy [x] (prn x) x)

(defn seqs-match? [a b]
  ;; we don't care about size because the input might be an infinite seq
  (every? (partial apply =) (zipmap a b)))

(defn check-fn [f]
  (doseq [{:keys [testcase expected]} [{:testcase [3 [:a :b :c :d :e :f :g]], :expected [:a :b :d :e :g]}
                                       {:testcase [3 [:a :b :c :d :e :f :g :h :i]], :expected [:a :b :d :e :g :h]}
                                       {:testcase [3 [:a :b]], :expected [:a :b]}
                                       {:testcase [3 (range)], :expected [0 1 3 4 6 7 9 10 12]}
                                       {:testcase [3 []], :expected []}]
          :let [actual (apply f testcase)]]
    (if (seqs-match? actual expected)
      (println "Match!")
      (do (println "Mismatch :(")
          (println (str "  Expected: " (pr-str expected)))
          (println (str "  Actual  : " (pr-str actual)))))))

The simplest way I could think of was indexing the collection and filtering it:

(defn drop-every-simple [n coll]
  (->> coll
       (map-indexed (fn [index v] [(mod (inc index) n) v]))
       (filter (complement #(-> % first zero?)))
       (map second)))

(check-fn drop-every-simple)

We can also write an arity to return an xform for it:

(defn drop-every
  ([n]
   (comp
    (map-indexed (fn [index v]  [(mod (inc index) n) v]))
    (filter (complement #(-> % first zero?)))
    (map second)))
  ([n coll]
   (sequence (drop-every n) coll)))

(check-fn drop-every)
(defn drop-every [n s]
(mapcat (partial take (dec n))
(partition n s)))
(defn drop-every [n values]
(flatten (map #(take (- n 1) %) (partition-all n values))))
(defn drop-every
[n seq]
(let [seq-indexed (map-indexed #(vec [%1 %2]) seq)]
(for [[index val] seq-indexed
:when (or (< (inc index) n)
(not (= 0 (mod (inc index) n))))]
val)))
(defn drop-every
[n coll]
(->> coll
(partition-all (dec n) n)
flatten))
(drop-every 3 [:a :b :c :d :e :f :g])
;; => (:a :b :d :e :g)
(defn drop-every-nth [n coll]
(lazy-seq
(when-let [vals (seq coll)]
(concat (take (dec n) coll)
(drop-every-nth n (drop n coll))))))
(defn drop-every [nth coll]
(keep-indexed #(when (< 0 (rem (inc %1) nth)) %2) coll))
(defn drop-every [n xs]
(->> xs
(map vector (range))
(map (fn [[a b]] [(inc a) b]))
(filter (fn [[a b]] (not= 0 (mod a n))))
(map (fn [[a b]] b))))
(defn drop-every
([n]
{:pre [(pos-int? n)]}
(fn [rf]
(let [iv (volatile! n)]
(fn
([] (rf))
([result] (rf result))
([result input]
(if (zero? (vswap! iv unchecked-dec))
(do (vreset! iv n) result)
(rf result input)))))))
([n coll]
(sequence (drop-every n) coll)) )
(defn drop-every
[n coll]
(->> coll
(cons :dummy-1st-element)
(partition-all n)
(mapcat rest)))
(defn drop-every [n coll]
(apply concat (partition-all (dec n) n coll)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.