Marco Grassi
Marco Grassi

Reputation: 89

Find elements in list and also keep adjacent element

i have a list like '(1 2 3 1 4 1 1 6 8 9 0 1) (not actually of numbers, just as an example)

I want to keep all "1" and the element next to the "1".

So the result i would want is (1 2 1 4 1 1 6 1).

Coming from an imperative point of view i would iterate over the list with a for loop, find the "1" at a certain index i and then also keep the element at index i+1.

What would a functional, Clojure idiomatic way of solving this problem be?

Upvotes: 2

Views: 492

Answers (8)

jas
jas

Reputation: 10865

Using reduce you can move along the original list building a new list as you go. The reducing function f is passed the new list up until now and the next element from the old list. If the list up until now ends with a 1, or the next element is a 1, add the element to the new list. Otherwise keep the new list as is and move along.

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]

Upvotes: 4

Gwang-Jin Kim
Gwang-Jin Kim

Reputation: 9865

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

A functional solution using only cons first last butlast list map filter = and defn and def.

Upvotes: 0

akond
akond

Reputation: 16035

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

Upvotes: 0

Alan Thompson
Alan Thompson

Reputation: 29958

When you need to loop over data and retain state, I think a plain-old loop/recur is the most straightforward technique:

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

Upvotes: 0

leetwinski
leetwinski

Reputation: 17859

here's one more solution with clojure's seq processors composition:

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

Upvotes: 1

Taylor Wood
Taylor Wood

Reputation: 16194

I think I'd prefer reduce for something like this, but here's another 'functional' way of looking at it:

You have a sequence of values that should produce a potentially smaller sequence of values based on some predicate (i.e. filtering) and that predicate needs look-ahead/-behind behavior.

A less common use for map is mapping over multiple sequences at once e.g. (map f coll1 coll2 coll3). If you pass in an "offset" version of the same collection it can be used for the look-ahead/-behind logic.

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

This is (ab)using mapcat behavior to combine the mapping/filtering into one step, but it could also be phrased with map + filter.

Upvotes: 2

amalloy
amalloy

Reputation: 91857

When you can't think of anything clever with sequence combinators, write the recursion by hand. It's not exactly elegant, but it's lazy:

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

Upvotes: 2

user2609980
user2609980

Reputation: 10484

Another idea that does not work since it misses a last one:

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

So use two arity version of mapcat over the vector and the vector shifted one to the right.

You could check that last 1 explicitly and add, then you get a less elegant working version:

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

Upvotes: 0

Related Questions