2

Is there any built-in function in LISP languages (or Racket in particular) that would work like map, but pass index of the element as one of the arguments to the mapping function?

Example of such function would be:

(define map-index (lambda (func list)
  (map func list (build-list (length list) (lambda (i) i)))))

;usage:
> (map-index cons '(a b c d))
;output:
'((a . 0) (b . 1) (c . 2) (d . 3))

Obviously this is not a very efficient implementation and doesn't support multiple lists as arguments, like regular map does.

2 Answers 2

5

Racket


You can write a dead-simple version of map-index using the Racket range procedure and mapping over the result:

(define (map-index-1 f xs)
  (map f xs (range (length xs))))

In some situations you might want the indices first:

(define (map-index-2 f xs)
  (map f (range (length xs)) xs))

If you want to be able to use map-index over multiple lists, you can pass the list arguments to an optional parameter. Here, apply applies the map procedure to a list constructed from the function f, the input lists, and a range list:

(define (map-index-3 f . xs)
  (apply map (cons f
                   (append xs
                           (list (range (length (car xs))))))))

But it might make more sense to place the indices first when mapping over multiple lists:

(define (map-index-4 f . xs)
  (apply map (cons f
                   (cons (range (length (car xs)))
                         xs))))
scratch.rkt> (map-index-1 cons '(a b c d))
'((a . 0) (b . 1) (c . 2) (d . 3))

scratch.rkt> (map-index-2 cons '(a b c d))
'((0 . a) (1 . b) (2 . c) (3 . d))

scratch.rkt> (map-index-3 list '(a b c d) '(one two three four) '(w x y z))
'((a one w 0) (b two x 1) (c three y 2) (d four z 3))

scratch.rkt> (map-index-4 list '(a b c d) '(one two three four) '(w x y z))
'((0 a one w) (1 b two x) (2 c three y) (3 d four z))

Scheme


Standard Scheme does not have a built-in range procedure, but it is easy enough to write a simple version. These solutions will work on any R4RS, R5RS, R6RS, or R7RS Scheme implementation. This version of range does more than is required for the current application, taking a step argument which can be positive or negative:

(define (range start stop step)
  (if (or (and (> step 0)
               (>= start stop))
          (and (<= step 0)
               (<= start stop)))
      '()
      (cons start (range (+ start step) stop step))))

Having defined a range procedure, the same approach used for the Racket solutions above can be used in Scheme:

(define (map-index-5 f xs)
  (map f xs (range 0 (length xs) 1)))

(define (map-index-6 f xs)
  (map f (range 0 (length xs) 1) xs))

(define (map-index-7 f . xs)
  (apply map (cons f
                   (append xs
                           (list (range 0 (length (car xs)) 1))))))

(define (map-index-8 f . xs)
  (apply map (cons f
                   (cons (range 0 (length (car xs)) 1)
                         xs))))
> (map-index-5 cons '(a b c d))
((a . 0) (b . 1) (c . 2) (d . 3))

> (map-index-6 cons '(a b c d))
((0 . a) (1 . b) (2 . c) (3 . d))

> (map-index-7 list '(a b c d) '(one two three four) '(w x y z))
((a one w 0) (b two x 1) (c three y 2) (d four z 3))

> (map-index-8 list '(a b c d) '(one two three four) '(w x y z))
((0 a one w) (1 b two x) (2 c three y) (3 d four z))

A map-range Procedure for Standard Scheme


This method can be extended to use numbers in a more complex range that may not represent indices by taking advantage of the capabilities of a range function. Using the range definition from above, this map-range procedure still works on R4RS to R7RS Scheme implementations:

(define (map-range f start step . xs)
  (let ((stop (+ start (* step (length (car xs))))))
    (apply map (cons f
                     (cons (range start stop step)
                           xs)))))
> (map-range cons 2 2 '(a b c d))
((2 . a) (4 . b) (6 . c) (8 . d))

> (map-range list 5 5 '(a b c d) '(one two three four) '(w x y z))
((5 a one w) (10 b two x) (15 c three y) (20 d four z))

> (map-range cons 2 -2 '(a b c d))
((2 . a) (0 . b) (-2 . c) (-4 . d))

> (map-range list 5 -5 '(a b c d) '(one two three four) '(w x y z))
((5 a one w) (0 b two x) (-5 c three y) (-10 d four z))
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for elaborate answer. I think for/list would be preferred in Racket, in other dialects this might be the way though. I'm just not sure about performance, AFAIK this could be implemented using foldl/foldr sacrificing readability in the process.
@AdamŠtafa -- for/list is not available in any other Scheme dialect AFAIK; the main reason I wrote this answer was to show how your goal might be accomplished in portable Scheme. But for the simplest case of map-index I would probably reach for (map f xs (range (length xs)), even in Racket. I personally find it more concise and legible than (for/list ([(x i) (in-indexed lst)]) (f x i)); I would be surprised if there is a significant performance difference, but you could always measure and compare.
2

Not exactly, but there are other things for similar goals, like for/list with in-naturals or in-indexed.

For example instead of (map-index f lst), the pattern would be

(for/list ([x lst] [i (in-naturals)]) (f x i))

or

(for/list ([(x i) (in-indexed lst)]) (f x i))

And either of those patterns could be used to implement map-index as well as your combination of map and build-list.

Racket's iteration forms like for/list are more flexible than a fixed set of map-like functions.

Concrete Examples:

> (for/list ([x '(a b c d)] [i (in-naturals)]) (cons x i))
'((a . 0) (b . 1) (c . 2) (d . 3))
> (for/list ([(x i) (in-indexed '(a b c d))]) (cons x i))
'((a . 0) (b . 1) (c . 2) (d . 3))

Or if you still want a map-index function you could define it more succinctly using this.

> (define (map-index f lst)
    (for/list ([(x i) (in-indexed lst)]) (f x i)))
> (map-index cons '(a b c d))
'((a . 0) (b . 1) (c . 2) (d . 3))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.