Search code examples
listschemeracketminimum

Position of minimum element in list


I would like to write a Racket function that takes a list and returns the position of the smallest element of that list. I already wrote a function that works:

  (define (min-position xs)
    (define (min-position2 count pos xs)
      (cond ((null? xs) #f)
            ((= 1 (length xs)) pos)
            ((< (car xs) (cadr xs))
             (min-position2 (+ count 1) pos (cons (car xs) (cddr xs))))
            (else (min-position2 0 (+ count pos 1) (cons (cadr xs) (cddr xs))))))
    (min-position2 0 0 xs))

Example inputs and outputs:

> (min-position '(9 8 7 6 5))
4
> (min-position '(9 8 1 6 5))
2
> (min-position '(0 1 2))
0

But is there a more elegant way to write this?


Solution

  • Well, it all depends on your definition of elegant. For me, an elegant solution is one that is short, clear, idiomatic and uses existing procedures (that is, it doesn't reinvent the wheel). Here's my shot:

    (require srfi/1)  ; import `list-index`
    (require srfi/26) ; import `cute`
    
    (define (min-position lst)
      (and (not (null? lst))
           (list-index (cute = (apply min lst) <>) lst)))
    

    Here's how it works:

    • (apply min lst) finds the minimum element in the list, using the built-in min procedure
    • (cute = (apply min lst) <>) uses cute for creating a specialized predicate that will return #t whenever an element is equal to the minimum, making sure that we find the minimum only once
    • (list-index (cute = (apply min lst) <>) lst) uses list-index with the previous predicate to find the index with the first minimum element in the list
    • The (and (not (null? lst)) … ) part is there for handling the edge case where the input list is empty

    Short and sweet. The only disadvantage is that it traverses the input list twice, once for finding the minimum element and once again for finding the index of that element. But that's a small price to pay, and it's still an O(n) solution that works as expected:

    (min-position '(9 8 7 6 5))
    => 4
    (min-position '(9 8 1 6 5))
    => 2
    (min-position '(0 1 2))
    => 0
    (min-position '())
    => #f