94

Any ideas what ???? should be? Is there a built in? What would be the best way to accomplish this task?

(def v ["one" "two" "three" "two"])

(defn find-thing [ thing vectr ]
  (????))

(find-thing "two" v) ; ? maybe 1, maybe '(1,3), actually probably a lazy-seq
John Lawrence Aspden
  • 17,124
  • 11
  • 67
  • 110

9 Answers9

151

Built-in:

user> (def v ["one" "two" "three" "two"])
#'user/v
user> (.indexOf v "two")
1
user> (.indexOf v "foo")
-1

If you want a lazy seq of the indices for all matches:

user> (map-indexed vector v)
([0 "one"] [1 "two"] [2 "three"] [3 "two"])
user> (filter #(= "two" (second %)) *1)
([1 "two"] [3 "two"])
user> (map first *1)
(1 3)
user> (map first 
           (filter #(= (second %) "two")
                   (map-indexed vector v)))
(1 3)
Brian Carper
  • 71,150
  • 28
  • 166
  • 168
49

Stuart Halloway has given a really nice answer in this post http://www.mail-archive.com/clojure@googlegroups.com/msg34159.html.

(use '[clojure.contrib.seq :only (positions)])
(def v ["one" "two" "three" "two"])
(positions #{"two"} v) ; -> (1 3)

If you wish to grab the first value just use first on the result.

(first (positions #{"two"} v)) ; -> 1

EDIT: Because clojure.contrib.seq has vanished I updated my answer with an example of a simple implementation:

(defn positions
  [pred coll]
  (keep-indexed (fn [idx x]
                  (when (pred x)
                    idx))
                coll))
ponzao
  • 20,684
  • 3
  • 41
  • 58
  • Very nice! This is the sort of answer I was expecting. – John Lawrence Aspden Jan 28 '11 at 17:32
  • 2
    Not that it affects the merit of this answer, but seq-utils has been changed just clojure.contrib.seq now. – John Lawrence Aspden Jan 28 '11 at 17:33
  • where to get `clojure.contib.seq` in clojure 1.6? No library in list: http://dev.clojure.org/display/community/Where+Did+Clojure.Contrib+Go – d9k Oct 12 '14 at 14:50
  • @d9k, "If a clojure.contrib namespace is listed here but has no migration details, that means no one has volunteered to maintain that namespace." I added an example implementation for `positions`. – ponzao Oct 13 '14 at 10:44
29
(defn find-thing [needle haystack]
  (keep-indexed #(when (= %2 needle) %1) haystack))

But I'd like to warn you against fiddling with indices: most often than not it's going to produce less idiomatic, awkward Clojure.

cgrand
  • 7,939
  • 28
  • 32
  • Oh nice 'when'! I agree about indices generally, but I have a csv file, and the names of the fields are in the header, and I want to get the field "field" from each row, so what I'm doing is looking "field" up in the header, and then nthing the rows. I can think of strange things to do with interleave, but is there a nice way that doesn't use explicit indices that's readable? – John Lawrence Aspden Jan 28 '11 at 17:38
  • 8
    When I have that use-case - csv headers - I have just built a map to do the lookup (assuming unique column headers). The map _is_ then my function to do index lookup. (let [header-index (zipmap header-vector (iterate inc 0))] ...) – Alex Stoddard Jan 28 '11 at 18:04
  • 1
    Wow. You answered the question I should have asked! – John Lawrence Aspden Jan 28 '11 at 18:32
  • 3
    Well, I would have proposed something very semblable to Alex's solution. (-> "colname" header-index row) and you have your value. – cgrand Jan 28 '11 at 18:36
14

As of Clojure 1.4 clojure.contrib.seq (and thus the positions function) is not available as it's missing a maintainer: http://dev.clojure.org/display/design/Where+Did+Clojure.Contrib+Go

The source for clojure.contrib.seq/positions and it's dependency clojure.contrib.seq/indexed is:

(defn indexed
  "Returns a lazy sequence of [index, item] pairs, where items come
  from 's' and indexes count up from zero.

  (indexed '(a b c d))  =>  ([0 a] [1 b] [2 c] [3 d])"
  [s]
  (map vector (iterate inc 0) s))

(defn positions
  "Returns a lazy sequence containing the positions at which pred
   is true for items in coll."
  [pred coll]
  (for [[idx elt] (indexed coll) :when (pred elt)] idx))

(positions #{2} [1 2 3 4 1 2 3 4]) => (1 5)

Available here: http://clojuredocs.org/clojure_contrib/clojure.contrib.seq/positions

lsh
  • 658
  • 5
  • 12
  • 2
    Thanks for posting this version. Since 1.2 you can also replace (iterate inc 0) with simply (range). – dribnet Jan 22 '13 at 00:21
7

I was attempting to answer my own question, but Brian beat me to it with a better answer!

(defn indices-of [f coll]
  (keep-indexed #(if (f %2) %1 nil) coll))

(defn first-index-of [f coll]
  (first (indices-of f coll)))

(defn find-thing [value coll]
  (first-index-of #(= % value) coll))

(find-thing "two" ["one" "two" "three" "two"]) ; 1
(find-thing "two" '("one" "two" "three")) ; 1

;; these answers are a bit silly
(find-thing "two" #{"one" "two" "three"}) ; 1
(find-thing "two" {"one" "two" "two" "three"}) ; nil
John Lawrence Aspden
  • 17,124
  • 11
  • 67
  • 110
5

Here's my contribution, using a looping structure and returning nil on failure.

I try to avoid loops when I can, but it seems fitting for this problem.

(defn index-of [xs x]
  (loop [a (first xs)
         r (rest xs)
         i 0]
    (cond
      (= a x)    i
      (empty? r) nil
      :else      (recur (first r) (rest r) (inc i)))))
Josh.F
  • 3,666
  • 2
  • 27
  • 37
2

I recently had to find indexes several times or rather I chose to since it was easier than figuring out another way of approaching the problem. Along the way I discovered that my Clojure lists didn't have the .indexOf(Object object, int start) method. I dealt with the problem like so:

(defn index-of
"Returns the index of item. If start is given indexes prior to
 start are skipped."
([coll item] (.indexOf coll item))
([coll item start]
  (let [unadjusted-index (.indexOf (drop start coll) item)]
    (if (= -1 unadjusted-index)
  unadjusted-index
  (+ unadjusted-index start)))))
Joshua
  • 547
  • 1
  • 5
  • 13
1

We don't need to loop the whole collection if we need the first index. The some function will short circuit after the first match.

(defn index-of [x coll]
  (let [idx? (fn [i a] (when (= x a) i))]
  (first (keep-indexed idx? coll))))
Vikas Gautam
  • 1,793
  • 22
  • 21
0

I'd go with reduce-kv

(defn find-index [pred vec]
  (reduce-kv
    (fn [_ k v]
      (if (pred v)
        (reduced k)))
    nil
    vec))
Light-wharf
  • 147
  • 1
  • 7