2

I want to implement an org-agenda-cmp-user-defined for use with org-agenda-sorting-strategy and user-defined-up / user-defined-down.

In order to do that, I have two lists of tags I need to compare, from the two items to be sorted passed to org-agenda-cmp-user-defined. I want the function to compare based on a particular order of tags, say:

("spam" "ham" "eggs")

So that the items are sorted by whether they have tag "spam" first, then breaking ties by whether they have tag "ham", etc.

How can I do that, in good elisp style? I'd like it to be reasonably CPU-efficient, since org-agenda already does quite a bit of work building its views.

I'm aware that org agenda has a built-in way of sorting based on the first tag on each item: note this is different from that behaviour.

For example, these sets of tags are sorted according to the priority list above (("spam") and ("lettuce" "spam") are equal for sorting purposes, the others are all distinct for sorting purposes):

("spam" "ham" "eggs")
("spam" "ham")
("spam")
("lettuce" "spam")
("ham")
("eggs")
nil

Of course, org-agenda-cmp-user-defined would see only one pair of these values at a time.

2

Dunno anything about org-agenda-cmp-user-defined etc. But if the question is just about comparing two equal-length lists of strings, in order, until the first difference is found, and returning -1 if the string from the first list is less than that of the second list, returning 1 if the reverse is true, and returning nil if there is no difference, then this should do the job:

(defun my-agenda-cmp (list1 list2)
  "Compare two same-length lists of strings, LIST1 and LIST2.
Compare strings from each, in order.
Return nil if they have the same strings in the same order.
Otherwise, for the first difference encountered, return:
 -1 if LIST1's string is less than LIST2's string.
  1 if LIST2's string is less than LIST1's string."
  (let* ((l1  list1)
         (l2  list2)
         (x1  (car l1))
         (x2  (car l2))
         cmp)
    (catch 'my-agenda-cmp
      (while l1
        (setq cmp  (compare-strings x1 nil nil x2 nil nil))
        (cond ((eq t cmp)
               (setq l1  (cdr l1)
                     l2  (cdr l2)
                     x1  (car l1)
                     x2  (car l2)))
              ((> cmp 0) (throw 'my-agenda-cmp  1))
              ((< cmp 0) (throw 'my-agenda-cmp -1))))
      nil)))

On the other hand, if the two lists are not guaranteed to be of equal length then this should do the job:

(defun my-agenda-cmp (list1 list2)
  "Compare two lists of strings, LIST1 and LIST2.
Return -1 if LIST1 is shorter.  Return 1 if LIST2 is shorter.
Otherwise, compare strings from each, in order.

Return nil if they have the same strings in the same order.
Otherwise, for the first difference encountered, return:
 -1 if LIST1's string is less than LIST2's string.
  1 if LIST2's string is less than LIST1's string."
  (let* ((l1  list1)
         (l2  list2)
         (x1  (car l1))
         (x2  (car l2))
         cmp)
    (catch 'my-agenda-cmp
      (while (and x1  x2)
        (setq cmp  (compare-strings x1 nil nil x2 nil nil))
        (cond ((eq t cmp)
               (setq l1  (cdr l1)
                     l2  (cdr l2)
                     x1  (car l1)
                     x2  (car l2)))
              ((> cmp 0) (throw 'my-agenda-cmp  1))
              ((< cmp 0) (throw 'my-agenda-cmp -1))))
      (and (or  x1  x2)                 ; same length  => nil
           (and x1  1)                  ; L1 is longer =>  1
           -1))))                       ; L2 is longer => -1

In both versions the main part of the code just goes down the lists calling compare-strings. As soon as the first difference is encountered the function returns the result.

  • This is a useful function, whose lack prompted me to ask the question, thanks! It isn't an answer to the question I had in mind. Here's one way I could use it to solve the original problem: given two lists of tags to compare, for each list: map the priorities (("spam" "ham" "eggs") in my question) to a list of nils and ts specifying if that tag appears in the input list of tags. So ("ham" "lettuce") gives (nil t nil), nil and ("lettuce") both give (nil nil nil) and ("eggs" "spam") gives (t nil t). Do that for both tags lists, then compare the results with your function. – Croad Langshan Feb 17 at 18:23
  • Actually though, I guess I could use integers instead of lists if I did it that way (basically, interpret the t / nil lists from my previous comment as binary numbers). Also, I wonder if there's a way to do the mapping I describe in a neat way in elisp -- or a different approach. – Croad Langshan Feb 17 at 18:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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