0

In the following example, there are two criteria for sorting -- orders with an inactive status should appear last in the last, and all orders should be organized by order number from smallest to largest. The problem with the current approach is that the orders with an inactive status are not being sorted by their order numbers. How can a user programmatically specify the order in which sorts are performed? E.g., first sort by status, and then sort everything by number; or, first sort everything by number, and then sort by status without altering the initial sort.

(let ((seq '((5996635507 (((rank . parent) (status . "CANCELED")) (misc ."misc")))
             (5996635504 (((rank . parent) (status . "PENDING_ACTIVATION")) (misc ."misc")))
             (5996635414 (((rank . child) (status . "AWAITING_PARENT_ORDER")) (misc ."misc")))
             (5996635416 (((rank . grandchild) (status . "AWAITING_PARENT_ORDER")) (misc ."misc")))
             (5996635415 (((rank . grandchild) (status . "AWAITING_PARENT_ORDER")) (misc ."misc")))
             (5996635413 (((rank . parent) (status . "PENDING_ACTIVATION")) (misc ."misc")))
             (5996635115 (((rank . parent) (status . "CANCELED")) (misc ."misc")))))
      (inactive-list '("REJECTED" "CANCELED" "REPLACED" "FILLED" "EXPIRED")))
  (sort seq (lambda (a b)
              (let* ((order-number-a (car a))
                     (order-number-b (car b))
                     (alist-a (caadr a))
                     (alist-b (caadr b))
                     (status-a (cdr (assq 'status alist-a)))
                     (status-b (cdr (assq 'status alist-b))))
                (and (< order-number-a order-number-b)
                     (not (member status-a inactive-list)))))))

CURRENT RESULT: Order 5996635507 is before order 5996635115.

((5996635413 (((rank . parent) (status . "PENDING_ACTIVATION")) (misc . "misc")))
 (5996635414 (((rank . child) (status . "AWAITING_PARENT_ORDER")) (misc . "misc")))
 (5996635415 (((rank . grandchild) (status . "AWAITING_PARENT_ORDER")) (misc . "misc")))
 (5996635416 (((rank . grandchild) (status . "AWAITING_PARENT_ORDER")) (misc . "misc")))
 (5996635504 (((rank . parent) (status . "PENDING_ACTIVATION")) (misc . "misc")))
 (5996635507 (((rank . parent) (status . "CANCELED")) (misc . "misc")))
 (5996635115 (((rank . parent) (status . "CANCELED")) (misc . "misc"))))

DESIRED RESULT: Order 5996635507 is after order 5996635115.

((5996635413 (((rank . parent) (status . "PENDING_ACTIVATION")) (misc . "misc")))
 (5996635414 (((rank . child) (status . "AWAITING_PARENT_ORDER")) (misc . "misc")))
 (5996635415 (((rank . grandchild) (status . "AWAITING_PARENT_ORDER")) (misc . "misc")))
 (5996635416 (((rank . grandchild) (status . "AWAITING_PARENT_ORDER")) (misc . "misc")))
 (5996635504 (((rank . parent) (status . "PENDING_ACTIVATION")) (misc . "misc")))
 (5996635115 (((rank . parent) (status . "CANCELED")) (misc . "misc")))
 (5996635507 (((rank . parent) (status . "CANCELED")) (misc . "misc"))))

SECOND ATTEMPT:

(let ((seq '((5996635507 (((rank . parent) (status . "CANCELED")) (misc ."misc")))
             (5996635504 (((rank . parent) (status . "PENDING_ACTIVATION")) (misc ."misc")))
             (5996635414 (((rank . child) (status . "AWAITING_PARENT_ORDER")) (misc ."misc")))
             (5996635416 (((rank . grandchild) (status . "AWAITING_PARENT_ORDER")) (misc ."misc")))
             (5996635415 (((rank . grandchild) (status . "AWAITING_PARENT_ORDER")) (misc ."misc")))
             (5996635413 (((rank . parent) (status . "PENDING_ACTIVATION")) (misc ."misc")))
             (5996635115 (((rank . parent) (status . "CANCELED")) (misc ."misc")))))
      (inactive-list '("REJECTED" "CANCELED" "REPLACED" "FILLED" "EXPIRED")))
  (sort seq (lambda (a b)
              (let* ((order-number-a (car a))
                     (order-number-b (car b))
                     (alist-a (caadr a))
                     (alist-b (caadr b))
                     (status-a (cdr (assq 'status alist-a)))
                     (status-b (cdr (assq 'status alist-b))))
                (cond
                  ((and (not (member status-a inactive-list))
                        (not (member status-b inactive-list)))
                        (< order-number-a order-number-b))
                  ((and (member status-a inactive-list)
                        (member status-b inactive-list))
                        (< order-number-a order-number-b))
                  ((and (member status-a inactive-list)
                        (not (member status-b inactive-list)))
                    nil)
                  ((and (not (member status-a inactive-list))
                        (member status-b inactive-list))
                    t))))))
1
  • 1
    I would do two sorts, the first by order number, the second by active status (making sure that at least the second is a stable sort). Alternatively, split it into active and inactive bunches, sort each one by order number and concatenate.
    – NickD
    Jan 23, 2022 at 22:14

1 Answer 1

1

Your predicate function just needs to return non-nil if the first element should sort before the second.

E.g., first sort by status, and then sort everything by number

  • if A.status < B.status, return non-nil
  • if A.status > B.status, return nil
  • if A.status = B.status, test the number and return based on that.

The logic is entirely up to you.

The problem with the current approach is that the orders with an inactive status are not being sorted by their order numbers

You are returning nil whenever A is 'inactive', regardless of other criteria.

3
  • Thank you ... I updated the question with a second try, but it doesn't look right ... although the end result seems to be correct. The third test test condition looks like it should be non-nil; and, the fourth test condition looks like it should be nil -- but if I change those around to what seems to make sense, then the list looks inverted. I wasn't able to use a defvar to create a reusable test sequence because sort is destructive, so I just let-bound the entire sequence for the new test.
    – lawlist
    Jan 24, 2022 at 0:04
  • All those double-negatives in your comparison code... that could be much cleaner. I suggest you bind active-a and active-b variables, and maybe make the values integers so you can just compare them numerically.
    – phils
    Jan 24, 2022 at 0:44
  • "The third test test condition looks like it should be non-nil" -- The 3rd condition is (and (member status-a inactive-list) (not (member status-b inactive-list))) which is "A inactive; B active"; so if you want active items to appear before inactive items the correct return value is nil. When your own code is so confusing that you can't see the logic, it's time to refactor for readability.
    – phils
    Jan 24, 2022 at 0:53

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.