通过常见的lisp中的两个属性进行排序

时间:2015-12-06 15:24:53

标签: sorting lisp common-lisp

我需要帮助按照常见的lisp中的两个属性进行排序。

说我有一个清单: (1 x)(2 y)(1 x)(2 x)(3 y)(2 y)我试图按字符串和整数排序。 因此结果将是(1 x)(1 x)(2 x)(2 y)(2 y)(3 y)

目前我可以按变量或数字排序,但不能同时排序。如果我输入(2 x)(1 x)(1 y)(2 x)(1 y),我会(1 Y)(1 Y)(2 X)(1 X)(2 X)返回而不是(1 Y)(1 Y)(1 X)(2 X)(2 X)

我使用的代码是:

(defun get-number (term)
  (destructuring-bind (number variable) term
    (declare (ignore variable))
    number))

(defun get-variable (term)
  (destructuring-bind (number variable) term
    (declare (ignore number))
    variable))

(defun varsort (p1)
    (sort (copy-list p1) 'string> :key 'get-variable))

我的问题是如何按整个术语排序,(1 X)不仅仅是1X

2 个答案:

答案 0 :(得分:2)

两个选项:

  • stable-sort根据varsort
  • get-number的结果
  • 定义要在sort中使用的自定义比较函数:

    ;; choose a better name
    (compare-by-string-and-number (x y)
      (let ((vx (get-variable x))
            (vy (get-variable y)))
        (or (string> vx vy)
            (and (string= vx vy)
                 (> (get-number x)
                    (get-number y))))))
    

Joshua's answer是编写泛型比较函数的好方法。因为你正在操纵元组,所以你可以更具体一点,并写下以下内容:

(defun tuple-compare (comparison-functions)
  (lambda (left right)
    (loop for fn in comparison-functions
          for x in left
          for y in right
          thereis (funcall fn x y)
          until (funcall fn y x))))

例如:

(sort (copy-seq #((1 2) (2 3) (1 3) (2 1)))
      (tuple-compare (list #'< #'<)))

=> #((1 2) (1 3) (2 1) (2 3))

您可以利用所涉及的列表具有不同的长度:例如,您只能通过提供单个比较函数来根据第一个参数进行排序。如果要将所有可用元素对与相同的比较函数进行比较,也可以创建循环列表。

(stable-sort (copy-seq #((1 2 4)  (1 3 6) (1 2 6) (2 3 4) (1 3) (2 1)))
             (tuple-compare (list* #'> (circular-list #'<))))

=> #((2 1) (2 3 4) (1 2 4) (1 2 6) (1 3 6) (1 3))

圆形列表可在alexandria中找到)

真正的词典排序方式可以确保较短的列表在较长的列表之前进行排序,前提是它们共享一个共同的前缀:例如,它会在(1 3)之前对(1 3 6)进行排序。可能的修改如下:

(defun tuple-compare (comparison-functions &optional lexicographic)
  (lambda (left right)
    (loop for fn in comparison-functions
          for (x . xr) on left
          for (y . yr) on right
          do (cond
               ((funcall fn x y) (return t))
               ((funcall fn y x) (return nil))
               ((and lexicographic yr (null xr)) (return t))))))

答案 1 :(得分:2)

您可以通过编写谓词来完成此操作。如果你有一个可以比较变量的谓词和一个可以比较系数的谓词,那么你可以轻松地创建一个新的谓词,用一个检查,如果第一个谓词提供明确的答案,则返回一个明确的答案,或者推迟到第二个谓词,如果没有。这也适用于其他应用程序:

(defun and-then (original-predicate next-predicate)
  "Returns a new predicate constructed from ORIGINAL-PREDICATE and
NEXT-PREDICATE.  The new predicate compares two elements, x and y, by
checking first with ORIGINAL-PREDICATE.  If x is less than y under
ORIGINAL-PREDICATE, then the new predicate returns true.  If y is less
than x under ORIGINAL-PREDICATE, then the new predicate returns false.
Otherwise, the new predicate compares x and y using NEXT-PREDICATE."
  (lambda (x y)
    (cond
      ((funcall original-predicate x y) t)
      ((funcall original-predicate y x) nil)
      (t (funcall next-predicate x y)))))

然后很容易调用(然后'变量&lt;'系数&lt;)。首先,一些访问器和谓词:

(defun term-coefficient (term)
  (first term))

(defun coefficient< (term1 term2)
  (< (term-coefficient term1)
     (term-coefficient term2)))

(defun term-variable (term)
  (second term))

(defun variable< (term1 term2)
  (string< (term-variable term1)
           (term-variable term2)))

现在测试:

(defparameter *sample*
  '((1 x)(2 y)(1 x)(2 x)(3 y)(2 y)))
CL-USER> (sort (copy-list *sample*) 'coefficient<)
((1 X) (1 X) (2 Y) (2 X) (2 Y) (3 Y))

CL-USER> (sort (copy-list *sample*) 'variable<)
((1 X) (1 X) (2 X) (2 Y) (3 Y) (2 Y))

CL-USER> (sort (copy-list *sample*) (and-then 'variable< 'coefficient<))
((1 X) (1 X) (2 X) (2 Y) (2 Y) (3 Y))

你可以定义一个 compare-by 函数来创建一些这样的谓词函数,这可以使它们的定义更简单,或者可以完全删除。

(defun compare-by (predicate key)
  "Returns a function that uses PREDICATE to compare values extracted
by KEY from the objects to compare."
  (lambda (x y)
    (funcall predicate
             (funcall key x)
             (funcall key y))))

您可以简单地使用谓词定义:

(defun coefficient< (term1 term2)
  (funcall (compare-by '< 'term-coefficient) term1 term2))

(defun variable< (term1 term2)
  (funcall (compare-by 'string< 'term-variable) term1 term2))

或完全摆脱它们:

(defun varsort (p1)
  (sort (copy-list p1)
        (and-then (compare-by '<       'term-coefficient)
                  (compare-by 'string< 'term-variable))))