【问题标题】:Common Lisp Loop accumulator: minimize into multiple-value-bind?Common Lisp 循环累加器:最小化为多值绑定?
【发布时间】:2013-11-27 18:52:59
【问题描述】:
(defvar x '((5 . a) (3 . b) (1 . c) (9 . d)))
> X
(loop for i in x minimize (car i))
> 1

我想要的是得到C而不是1。我尝试使用值,因为它仍然会使用第一个返回值进行最小化,但我不知道是否有办法使用多值-在这种情况下绑定?

(loop for i in x
      minimize (values (car i) (cdr i)) into ans
      finally (return ans))

【问题讨论】:

    标签: loops common-lisp sbcl


    【解决方案1】:

    恐怕你得自己写代码:

    (let ((best ()))
      (dolist (pair x (cdr best))
        (when (or (null best) (< (car x) (car best)))
          (setq best pair))))
    

    这只会扫描列表一次。

    但是,这会多次调用密钥 (car)(如评论中所述)。

    这可以优化:

    (defun find-best (list &key (key #'identity) (test #'<))
      (when list
        (let* ((best (first list))
               (best-key (funcall key best)))
          (dolist (o (rest list) best)
            (let ((k (funcall key o)))
              (when (funcall test k best-key)
                (setq best o best-key k)))))))
    

    【讨论】:

    • 当 key 只是 car 时没那么重要,但这会在最佳值上多次调用 key 函数(在本例中为 car)。如果密钥有任何副作用,或者计算成本很高,那么将其最小化可能是值得的。 (当然,并非所有 CL 函数都这样做。例如,sort 的文档说“无法保证调用键的次数。”)
    【解决方案2】:
    (defvar x '((5 . a) (3 . b) (1 . c) (9 . d)))
    
    (loop for (num . sym) in x
          with min-num = nil
          with min-sym = nil
          do (when (or (null min-num)
                       (< num min-num))
               (setf min-num num
                     min-sym sym))
          finally (return (values min-sym min-num)))
    ;=> C, 1
    
    ;; see. http://common-lisp.net/project/iterate/doc/Finders.html#Finders
    (ql:quickload :iterate)
    (use-package :iterate)
    (iter (for (num . sym) in x)
          (finding sym minimizing num))
    ;=> C
    

    【讨论】:

    • 感谢 anon 指出iterate 包;我以前没用过,看起来很整洁——工具箱里的另一个工具:)
    【解决方案3】:

    由于这是一个最小化问题,您应该只需要遍历列表一次。您可以直接使用loopdotimes 或其他一些迭代结构相当容易地做到这一点。如果你想要一个更实用的方法,你可以使用这样的东西:

    (defun keyed-predicate (predicate key)
      (lambda (x y)
        (if (funcall predicate
                     (funcall key x)
                     (funcall key y))
            x
            y)))
    
    (cdr (reduce (keyed-predicate '< 'car) '((5 . a) (3 . b) (1 . c) (9 . d))))
    ;=> C
    

    不过,这样做的问题在于,它在某个点上“当前最佳值”的元素上多次调用 key 函数。这也发生在sds's answer 中(car 被多次调用 best, though it's not an issue withcar, sincecar` 没有任何副作用。例如,

    (cdr (reduce (keyed-predicate '< (lambda (x) 
                                       (format t "visiting ~a~%" x)
                                       (car x)))
                 '((5 . a) (3 . b) (1 . c) (9 . d))))
    ; visiting (3 . B)
    ; visiting (3 . B) ; repeat
    ; visiting (1 . C)
    ; visiting (1 . C) ; repeat
    ; visiting (9 . D)
    ;=> C
    

    最好确保在每个元素上只调用一次键函数,这在迭代实现中更容易。例如,

    (defun optimum (predicate list &key key)
      (flet ((key (x) 
               (if (null key) x
                   (funcall key x))))
        (if (endp list)
            (funcall predicate)
            (let* ((best (first list))
                   (best-key (key best)))
              (dolist (item (rest list) best)
                (let ((item-key (key item)))
                  (when (funcall predicate item-key best-key)
                    (setf best item
                          best-key item-key))))))))
    
    (cdr (optimum '< '((5 . a) (3 . b) (1 . c) (9 . d)) :key 'car))
    ;=> C
    

    在这里,每个项目只调用一次键:

    (cdr (optimum '< '((5 . a) (3 . b) (1 . c) (9 . d)) 
                  :key (lambda (x) 
                         (format t "visiting ~a~%" x)
                         (car x))))
    ; visiting (5 . A)
    ; visiting (3 . B)
    ; visiting (1 . C)
    ; visiting (9 . D)
    ;=> C
    

    【讨论】:

      【解决方案4】:
      CL-USER 15 > (defparameter *x* '((5 . a) (3 . b) (1 . c) (2 . d) (9 . e)))
      *X*
      
      CL-USER 16 > (loop with (min-n . min-v) = (first *x*)
                         for (n . v) in (rest *x*)
                         if (< n min-n) do (setf min-n n min-v v)
                         finally (return min-v))
      C
      

      作为一个函数:

      (defun minimize (list &key (pred #'<) (key #'identity))
        "returns values: the minimum value and if there was one"
        (if (null list)
            (values nil nil)
          (values (loop with min-e = (first list)
                        with min-v = (funcall key min-e)
                        initially (pop list)
                        for e in list 
                        for v = (funcall key e)
                        if (funcall pred v min-v) do (setf min-e e min-v v)
                        finally (return min-e))
                  t)))
      
      
      CL-USER 35 > (minimize '((5 . a) (3 . b) (1 . c) (2 . d) (9 . e)) :key #'car)
      (1 . C)
      T
      

      【讨论】:

      • ;简化和修复拼写错误 cadr/cdar (loop with x = '((5 . a) (3 . b) (1 . c) (9 . d)) with (min-n . min-v) = (first x) for (n . v) in (rest x) if (&lt; n min-n) do (setf min-v v) finally (return min-v))
      • 糟糕,将(setf min-v v) 更改为(setf min-n n min-v v)
      • @devon:对,我已经在函数中了。在 LOOP 中也发生了变化。
      猜你喜欢
      • 2013-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-13
      • 2019-09-29
      • 2021-09-12
      • 1970-01-01
      相关资源
      最近更新 更多