12

This is trivial implement of course, but I feel there is certainly something built in to Racket that does this. Am I correct in that intuition, and if so, what is the function?

Alex V
  • 3,416
  • 2
  • 33
  • 52

3 Answers3

19

Strangely, there isn't a built-in procedure in Racket for finding the 0-based index of an element in a list (the opposite procedure does exist, it's called list-ref). However, it's not hard to implement efficiently:

(define (index-of lst ele)
  (let loop ((lst lst)
             (idx 0))
    (cond ((empty? lst) #f)
          ((equal? (first lst) ele) idx)
          (else (loop (rest lst) (add1 idx))))))

But there is a similar procedure in srfi/1, it's called list-index and you can get the desired effect by passing the right parameters:

(require srfi/1)

(list-index (curry equal? 3) '(1 2 3 4 5))
=> 2

(list-index (curry equal? 6) '(1 2 3 4 5))
=> #f

UPDATE

As of Racket 6.7, index-of is now part of the standard library. Enjoy!

Óscar López
  • 232,561
  • 37
  • 312
  • 386
  • 2
    How bizarre. Would the racket-dev mailing list be the appropriate outlet for recommending this feature to be added to the language? – Alex V Apr 08 '13 at 04:15
  • 1
    I guess ... but it's not a big deal given that it's so simple to implement. – Óscar López Apr 08 '13 at 04:16
  • 2
    @Maxwell I had forgotten about `list-index`. See my updated answer. – Óscar López Apr 08 '13 at 04:29
  • 1
    Thanks Oscar, I am going to use that, you are amazing. – Alex V Apr 08 '13 at 04:43
  • 1
    You're the first non-Racket-dev I know who uses `curry`, congrats. :-) (I usually tend to use `cut` for similar situations, where true currying isn't required, because you can insert arguments in the middle, like [this](http://git.savannah.gnu.org/cgit/guile.git/commit/?h=stable-2.0&id=b6e374e535597448cb09588300d76b5d270d5d3a).) – C. K. Young Apr 08 '13 at 11:49
  • Note that `acc` is a pretty bad name here, since it doesn't really accumulate anything. – Eli Barzilay May 29 '13 at 03:29
  • @EliBarzilay agreed, fixed it! – Óscar López May 29 '13 at 03:32
  • @ÓscarLópez: Well, I'd have some other stylistic comments on your code, like the use of round parens, and the related newline in the `let` bindings, but I usually keep them to myself... – Eli Barzilay May 29 '13 at 03:34
  • @EliBarzilay I know, but old habits die hard – Óscar López May 29 '13 at 03:37
  • Why would you use list-index instead of car (filter ...)? – raffomania Dec 15 '13 at 21:48
  • @raffomania because `filter` returns a list of elements, we're interested in a single element. And besides, there's no way to specify an _index_ to be returned when using `filter`. Simply put, it's not the right tool for the job – Óscar López Dec 15 '13 at 23:06
  • @ChrisJester-Young Maybe [this](http://docs.racket-lang.org/srfi/srfi-std/srfi-26.html) is a better link for `cut`. – Fabien Mar 19 '14 at 12:31
  • @Fabien The intent was to present a real use case for `cut`, which involved linking to real code that used it. It wasn't to provide documentation for people who didn't already know what `cut` was. – C. K. Young Mar 19 '14 at 12:36
  • @ÓscarLópez I would suggest you to swap the two halves of your answer, for quick readers like me who stop reading before the end. I think the most important information is that there is a procedure. – Fabien Mar 19 '14 at 12:37
  • @Fabien I disagree. I think the most important information (wrt this answer) for a programmer to know is that he/she should feel empowered by their language to implement any procedure they feel is missing. If you later come to learn that a built-in procedure exists, you can use it in new programs and/or refactor old programs. Stopping to task the internet every time you need a basic procedure is only short-cutting yourself. – Mulan Dec 07 '16 at 17:12
  • 4
    As of Racket 6.7 `index-of` is now part of the standard library! This might be worthy of an edit (although this answer works perfectly fine). – djfdev Sep 28 '17 at 23:13
  • @ÓscarLópez thank you! I'm learning racket and your SO posts have been indispensable so far – djfdev Sep 29 '17 at 01:42
8

Here's a very simple implementation:

(define (index-of l x)
  (for/or ([y l] [i (in-naturals)] #:when (equal? x y)) i))

And yes, something like this should be added to the standard library, but it's just a little tricky to do so nobody got there yet.

Note, however, that it's a feature that is very rarely useful -- since lists are usually taken as a sequence that is deconstructed using only the first/rest idiom rather than directly accessing elements. More than that, if you have a use for it and you're a newbie, then my first guess will be that you're misusing lists. Given that, the addition of such a function is likely to trip such newbies by making it more accessible. (But it will still be added, eventually.)

Eli Barzilay
  • 29,301
  • 3
  • 67
  • 110
3

One can also use a built-in function 'member' which gives a sublist starting with the required item or #f if item does not exist in the list. Following compares the lengths of original list and the sublist returned by member:

(define (indexof n l)
  (define sl (member n l))
  (if sl 
      (- (length l)
         (length sl))
      #f))

For many situations, one may want indexes of all occurrences of item in the list. One can get a list of all indexes as follows:

(define (indexes_of1 x l)
  (let loop ((l l)
             (ol '())
             (idx 0))
    (cond
      [(empty? l) (reverse ol)]
      [(equal? (first l) x)
       (loop (rest l)
             (cons idx ol)
             (add1 idx))]
      [else
       (loop (rest l)
             ol
             (add1 idx))])))

For/list can also be used for this:

(define (indexes_of2 x l)
  (for/list ((i l)
             (n (in-naturals))
             #:when (equal? i x))
    n))

Testing:

(indexes_of1 'a '(a b c a d e a f g))
(indexes_of2 'a '(a b c a d e a f g))

Output:

'(0 3 6)
'(0 3 6)
rnso
  • 23,686
  • 25
  • 112
  • 234
  • 2
    Please please don't do the `-1` thing to signal that there is no index. It's much clearer and easier if you return `#false` in those cases, so that's the idiomatic way to do it – Alex Knauth Dec 08 '16 at 15:03