30

With Clojure, how do I find the first index with a positive value in this vector [-1 0 3 7 9]?

I know you can get the first result of something rather elegantly with first and filter:

(first (filter pos? [-1 0 99 100 101]))

This code returns the value 99. The answer I want is the index which is 2.

Dave Liepmann
  • 1,555
  • 1
  • 18
  • 22
  • See here: http://stackoverflow.com/questions/4830900/how-do-i-find-the-index-of-an-item-in-a-vector – nimrodm Dec 27 '11 at 05:02
  • My question is totally different. I've edited my title to reflect this. –  Dec 27 '11 at 05:09

6 Answers6

47

Using keep-indexed you can get a sequence of indices for which a predicate is satisfied:

(defn indices [pred coll]
   (keep-indexed #(when (pred %2) %1) coll))

With this simple function you'll solve your problem with the expression

user=> (first (indices pos? [-1 0 99 100 101]))
2

Note that, due to the lazyness of keep-indexed (and indices), the entire sequence need not be realized so no extraneous calculations are performed.

Jonas
  • 19,422
  • 10
  • 54
  • 67
  • 1
    This works great. (As I dug into it, I noticed that `keep-indexed` includes **non-nil** (which includes `false`) results. I don't know the rationale for this.) – David J. Jan 08 '13 at 07:07
3
(defn first-pos [x] 
  (loop [arr x n 0]
     (if (pos? (first arr))
     n
     (recur (next arr) (inc n)))))

This is a good example of using functional programming's powerful tail recursion.

Samiur
  • 76
  • 1
  • 3
  • It fails with a `NullPointerException` if there are no positive elements in the list; also OP was asking for the match of an arbitrary predicate, not only `pos?` – Óscar López Dec 27 '11 at 13:46
1

I'm a little late to the party, but I prefer:

(defn index-of-pred
  [pred coll]
  (ffirst (filter (comp pred second) (map-indexed list coll))))

;; example usage
(index-of-pred pos? [-1 -2 -5 0 3 4 1 -100])
;=> 4
solussd
  • 494
  • 5
  • 7
1
(defn pred-idx [pred [idx hist] cur]
  (if (pred cur)
    [(inc idx) (conj hist idx)]
    [(inc idx) hist]))

(defn idx-filter [pred col]
  (second (reduce (partial pred-idx pred) [0 []] col)))

(first (idx-filter pos? [-1 0 99 100 101]))
2

Not sure if this is better, but it works. I think it forces evaluation of the entire sequence though, and if you need all indices that would be better. The correct thing to do is probably turn it into a lazy sequence somehow, but I'm done for the evening.

BillRobertson42
  • 12,602
  • 4
  • 40
  • 57
1
(first (filter #(not (nil? %)) (map #(when (pos? %1) %2) [-1 1 0 99 100 101] (range))))

Map can take one or more collections and return one list,put condition on map,and filter nil.

0

Try this:

(defn first-index
  ([pred coll] (first-index coll pred 0))
  ([pred coll idx]
    (cond (= coll '()) -1
          (pred (first coll)) idx
          :else (recur pred (rest coll) (inc idx)))))

And use it like this:

(defn is-pos? [x]
  (> x 0))

(first-index is-pos? [-1 0 3 7 9])

It returns the zero-based index of the first element that satisfies the predicate (is-pos? in the example), or -1 if no element matches the predicate.

Óscar López
  • 232,561
  • 37
  • 312
  • 386
  • 1
    This function will only return -1 if passed `nil` explicitly, because `(rest x)` is never nil for any `x`. You should be calling `seq` on the collection before testing it for nil. Also, `[pred coll]` would be a friendlier argument order than `[coll pred]` - cf. `map` and `filter`, for example. – amalloy Dec 27 '11 at 06:42
  • this works (with amalloy's suggestions), but the version using keep-indexed is much simpler. – Gert Dec 27 '11 at 07:50
  • There, I've edited it according to @amalloy's comment. Coming from a Scheme background, it's weird that nil != '() in Clojure – Óscar López Dec 27 '11 at 13:32