列表中最小元素的位置

时间:2014-04-17 12:38:23

标签: list scheme racket minimum

我想写一个Racket函数,它接受一个列表并返回该列表中最小元素的位置。我已经编写了一个有效的函数:

  (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))

示例输入和输出:

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

但有更优雅的方式来写这个吗?

3 个答案:

答案 0 :(得分:3)

我不确定你所说的“优雅”是什么意思。例如,可能存在spiffier算法。但这是我如何在保留基本方法的同时使代码更具可读性(恕我直言)。

一步一步:

您的输入/输出示例,重写为check-equal?测试:

#lang racket
(require rackunit)
(define (test f)
  (check-equal? (f '(9 8 7 6 5)) 4)
  (check-equal? (f '(9 8 1 6 5)) 2)
  (check-equal? (f '(0 1)) 0)
  (check-equal? (f '(0 1 2)) 0))

您的原始文件,但使用[]代替()代表cond条款。

(define (min-position/v0 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))
(test min-position/v0)

使用match对列表进行细化,并使用thisnext等名称代替(car xs)(cadr xs)

(define (min-position/match xs)
  (define (min-position2 count pos xs)
    (match xs
      [(list) #f]
      [(list _) pos]
      [(list this next more ...)
       (cond [(< this next)
              (min-position2 (+ count 1) pos (cons this more))]
             [else
              (min-position2 0 (+ count pos 1) (cons next more))])]))
  (min-position2 0 0 xs))
(test min-position/match)

将内部功能更改为let loop ...。真的是一样的,只是更简洁一点。

(define (min-position/match&loop xs)
  (let loop ([count 0] [pos 0] [xs xs])
    (match xs
      [(list) #f]
      [(list _) pos]
      [(list this next more ...)
       (cond [(< this next) (loop (+ count 1) pos (cons this more))]
             [else          (loop 0 (+ count pos 1) (cons next more))])])))
(test min-position/match&loop)

同样,这是与原始算法相同的算法。但是我会发现很快就能轻松搞定。

答案 1 :(得分:2)

名为let 的是Scheme中常见的习语:

(define (min-position xs)
  (let loop ((xs xs) (pos 0) (mn #f) (mnpos #f))
    (if (null? xs)
        mnpos
        (let ((c (car xs)))
          (if (or (not mn) (< c mn))
              (loop (cdr xs) (add1 pos) c pos)
              (loop (cdr xs) (add1 pos) mn mnpos))))))

在Racket中,您还可以使用for/foldin-indexed来缩短代码:

(define (min-position xs)
  (define-values (mn mnpos)
    (for/fold ((mn #f) (mnpos #f)) (((c pos) (in-indexed xs)))
      (if (or (not mn) (< c mn))
          (values c pos)
          (values mn mnpos))))
  mnpos)

答案 2 :(得分:2)

嗯,这完全取决于你对优雅的定义。对我来说,一个优雅的解决方案是一个简短,清晰,惯用的解决方案,并使用现有的程序(也就是说,它不会重新发明轮子)。这是我的镜头:

(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)))

以下是它的工作原理:

  • (apply min lst)使用内置的min程序
  • 查找列表中的最小元素
  • (cute = (apply min lst) <>)使用cute创建一个专门的谓词,只要元素等于最小值,它就会返回#t,确保我们只找到最小值
  • (list-index (cute = (apply min lst) <>) lst)使用list-index与上一个谓词一起查找列表中第一个最小元素的索引
  • (and (not (null? lst)) … )部分用于处理输入列表为空的边缘情况

短而甜蜜。唯一的缺点是它遍历输入列表两次,一次用于查找最小元素,另一次用于查找该元素的索引。但这只是一个很小的代价,它仍然是O(n)解决方案,按预期工作:

(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