Visual LISP, AutoLISP and General Customization
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Sorting a List

14 REPLIES 14
Reply
Message 1 of 15
Redraiderr2009
1070 Views, 14 Replies

Sorting a List

I have a list that contains data like this,

"Tree-01-08-AA5"

"Tree-04-12-QV"

"Tree-10-30-QS"

 

How would I go about sorting this list by AA5, QV, QS?

 

I know how to use vl-sort and reverse, but I do not understand how I would go about this. 

 

Any help would be nice.

 

Thanks,

14 REPLIES 14
Message 2 of 15
Moshe-A
in reply to: Redraiderr2009

Hi,

 

that easy:

 

(defun sortFunc (keyStr1 keyStr2)
 (< (substr keyStr1 12) (substr keyStr2 12))
)

 

(vl-sort (list "Tree-01-08-AA5" "Tree-04-12-QV" "Tree-10-30-QS") 'sortFunc)

 

this will do as long as your string items has a fixed format

if it is not? than maybe you need to take the key string from the end

 

Cheers

Moshe

 

 

  

Message 3 of 15


@Redraiderr2009 wrote:

I have a list that contains data like this,

"Tree-01-08-AA5"

"Tree-04-12-QV"

"Tree-10-30-QS"

 

How would I go about sorting this list by AA5, QV, QS?

....


Here's one way, finding and sorting by the substring following the last hyphen in each item:

 

(vl-sort

  '("Tree-04-12-QV" "Tree-01-08-AA5" "Tree-10-30-QS")

  '(lambda (a b)

    (<

      (substr a (+ (vl-string-position (ascii "-") a 0 T) 2))

      (substr b (+ (vl-string-position (ascii "-") b 0 T) 2))

    ); <

  ); lambda

); vl-sort

 

returns

 
("Tree-01-08-AA5" "Tree-10-30-QS" "Tree-04-12-QV")

Kent Cooper, AIA
Message 4 of 15

Thanks for the help guys, now I am trying to sort a list of lists. I have a database list and in that data base I have a list of data elements. 

DATABASE list would contain:

(("00_CATEGORY_NUM" "CCC") ("SYM" "VI") ("QTY" 1) (COMMON_NAME "SOMEPLANT))

(("00_CATEGORY_NUM" "AAA") ("SYM" "QM3") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))

(("00_CATEGORY_NUM" "BBB") ("SYM" "SS4") ("QTY" 1) (COMMON_NAME "SOMEPLANT))

(("00_CATEGORY_NUM" "AAA") ("SYM" "QM6") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))

(("00_CATEGORY_NUM" "BBB") ("SYM" "SS4") ("QTY" 1) (COMMON_NAME "SOMEPLANT))

(("00_CATEGORY_NUM" "CCC") ("SYM" "VI") ("QTY" 1) (COMMON_NAME "SOMEPLANT))

 

What I need to do is sort them by "00-CATEGORY_NUM" first, then sort them by "SYM" second. I am not used to working with more than one list at a time. I can sort each row alphabetically but I cannot seem to do a double sort. I would post my code, but none of it works. 

Message 5 of 15
pbejse
in reply to: Redraiderr2009


@Redraiderr2009 wrote:

 

What I need to do is sort them by "00-CATEGORY_NUM" first, then sort them by "SYM" second. I am not used to working with more than one list at a time. I can sort each row alphabetically but I cannot seem to do a double sort. I would post my code, but none of it works. 


Define "double sort" for us

 

On the sample you posted, Sorting by  "00-CATEGORY_NUM"  and  "SYM" yields the same results

 

(defun sortp (sym lst)
(setq lst ;;;<--- variable for testing purposes;;;
(vl-sort lst '(lambda (a b)
(< (sym a)(sym b))))
)
;; for testing purposes ;;;
(foreach itm lst
(print itm))
(princ)
)

 

(setq datalist '(
(("00_CATEGORY_NUM" "CCC") ("SYM" "VI") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
(("00_CATEGORY_NUM" "AAA") ("SYM" "QM3") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
(("00_CATEGORY_NUM" "BBB") ("SYM" "SS4") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
(("00_CATEGORY_NUM" "AAA") ("SYM" "QM6") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
(("00_CATEGORY_NUM" "BBB") ("SYM" "SS4") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
(("00_CATEGORY_NUM" "CCC") ("SYM" "VI") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))))

 

(SORTP cadar datalist) and (SORTP cadadr datalist), both will give you:

 

(("00_CATEGORY_NUM" "AAA") ("SYM" "QM3") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
(("00_CATEGORY_NUM" "AAA") ("SYM" "QM6") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
(("00_CATEGORY_NUM" "BBB") ("SYM" "SS4") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
(("00_CATEGORY_NUM" "BBB") ("SYM" "SS4") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
(("00_CATEGORY_NUM" "CCC") ("SYM" "VI") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
(("00_CATEGORY_NUM" "CCC") ("SYM" "VI") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))

 

 

Can you post  the desired result or another example?

 

Message 6 of 15

Sorting on two keys is easily done, you just need to give the sort function a suitable comparision function:

 

(defun compare-2-keys (a b)
  ;; comparision function for sorting on a primary and secondary key
  (cond
    ((< (key1 a)(key1 b)) T)
    ((= (key1 a)(key1 b)) (< (key2 a)(key2 b)))
    (T nil)))

 - this assumes your values can be handled by < and =, otherwise you need to substitute your own functions for those, too.

- this requires you to write the functions to dig out the key values. If you wanted to get fancy, even those could be given as parameters to this.

 

--

Message 7 of 15

This is what I am meaning. 

Message 8 of 15


@Redraiderr2009 wrote:

This is what I am meaning. 


Martti's approach will do that.  Incorporating the particulars of your kind of list and the way you want to sort it into his framework in the context of a (vl-sort) function [and I altered some of the SYM entries to be like your posted image, rather than your original list, and I find martti's (T nil) line isn't needed]:

 

(setq testlist
  '(
    (("00_CATEGORY_NUM" "CCC") ("SYM" "VL") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
    (("00_CATEGORY_NUM" "AAA") ("SYM" "QM6") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
    (("00_CATEGORY_NUM" "BBB") ("SYM" "SS6") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
    (("00_CATEGORY_NUM" "AAA") ("SYM" "QM3") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
    (("00_CATEGORY_NUM" "BBB") ("SYM" "SS4") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
    (("00_CATEGORY_NUM" "CCC") ("SYM" "VI") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))

  ); list
); setq

 

(vl-sort
  testlist
  '(lambda (a b)
    (cond
      ((< (cadar a) (cadar b))); by second item in first sub-list
      ((= (cadar a) (cadar b)) (< (cadadr a) (cadadr b))); by second item in second sub-list
    ); cond
  ); lambda
); vl-sort

 

That returns:

 

(

(("00_CATEGORY_NUM" "AAA") ("SYM" "QM3") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
(("00_CATEGORY_NUM" "AAA") ("SYM" "QM6") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
(("00_CATEGORY_NUM" "BBB") ("SYM" "SS4") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
(("00_CATEGORY_NUM" "BBB") ("SYM" "SS6") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
(("00_CATEGORY_NUM" "CCC") ("SYM" "VI") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))
(("00_CATEGORY_NUM" "CCC") ("SYM" "VL") ("QTY" 1) (COMMON_NAME "SOMEPLANT"))

)

 

However, if you might get into multiple-digit numerical endings for the SYM entries, you would need a more sophisticated comparison.  If the "SS4" in the list were "SS24" instead, the above would put that before "SS6".

 

[It's not perhaps the best illustration, because as pbejse pointed out, sorting the list by only (cadadr) value would result in the same thing.  But if I change, for example, the "SS" parts to "CC", they still show up in the same place in this result, in their "BBB" category positions, whereas in a straight (cadadr) comparison, they would come first.]

Kent Cooper, AIA
Message 9 of 15

That is a problem that I have not anticipated. I myself like to label things 01 02 etc for that reason, but getting everyone else to may be a problem. 🙂

 

I will work on this. Basically the result of this is to create a table with a plant list that has quantities, area square footages, common name, scientific names, Sizes, Remarks, Categories, and a graphic symbol. I have been using data extractions, but they are just way too slow and cumbersome. I need a one click button to crate a plantlist. This will take hours off production times. 

Message 10 of 15


@Kent1Cooper wrote:


 and I find martti's (T nil) line isn't needed]:

  

 


 

 

 

This is just a matter of style: do we try for clarity by minimizing the program text,

or by explicitly showing all cases instead of trusting the reader to know the default behaviour of COND where no test is triggered or where there aren't any expressions following a test.

Usually I like to always have a T branch in a COND, even if it is just a "you should never reach this" alert. Makes maintenance easier in the long run.

 

--

Message 11 of 15

  (setq BlockDataBase
    '(
        (("00_CATEGORY_NUM" "CCC") ("BlockName" "Tree-03-08-CC8"))
	(("00_CATEGORY_NUM" "CCC") ("BlockName" "Tree-03-10-CC10"))
	(("00_CATEGORY_NUM" "AAA") ("BlockName" "Tree-02-08-QS3"))
	(("00_CATEGORY_NUM" "AAA") ("BlockName" "Tree-02-20-QS10"))
	(("00_CATEGORY_NUM" "SSS") ("BlockName" "Shrub-36-07-LPC"))
	(("00_CATEGORY_NUM" "SSS") ("BlockName" "Shrub-36-01-LP"))
	(("00_CATEGORY_NUM" "SSS") ("BlockName" "Shrub-36-03-L"))
	(("00_CATEGORY_NUM" "SSS") ("BlockName" "Shrub-30-07-KL"))
	(("00_CATEGORY_NUM" "SSS") ("BlockName" "Shrub-30-12-UP"))
	(("00_CATEGORY_NUM" "SSS") ("BlockName" "Shrub-30-08-TY"))
	(("00_CATEGORY_NUM" "HHH") ("BlockName" "Hatch-16-ATR@10"))
	(("00_CATEGORY_NUM" "HHH") ("BlockName" "Hatch-01-ZZ@4"))
	(("00_CATEGORY_NUM" "HHH") ("BlockName" "Hatch-05-AA@12"))
); list ); setq ; sort block data record alphabetically to put the category first ;; (setq BlockDataBase (vl-sort BlockDataBase '(lambda (A B) (cond ((< (cadar A) (cadar B))) ((= (cadar A) (cadar B)) (< (cadadr A) (cadadr B)) (< (substr (cadadr A) (+ (vl-string-position (ascii "-") (cadadr A) 0 T)2)) (substr (cadadr B) (+ (vl-string-position (ascii "-") (cadadr B) 0 T)2)) ) ) );_end cond );_end lambda );_end vl-sort );end setq

 

In the code above I am trying to sort this list and I need a way to get them to sort according to size. Please see the attached image to see the results of this sort. I basicially need "Tree-02-20-QS10" to come before "Tree-02-08-QS3". The sort will put the 1 (in ten) ahead of the 3 even though the 10 is a higher number than 3. I am thinking that if one has more charcters than the other then it should sort the list by character length. I do not know enough about list manipulation to accomplish this.  I am wondering if anyone has any insight into this or if someone has run into this and had a solution. 

 

Thanks in advance. 

 

Russell

Message 12 of 15

Sort of works but I am missing something.

 

(setq BlockDataBase
'(
(("00_CATEGORY_NUM" "CCC") ("BlockName" "Tree-03-08-CC8"))
(("00_CATEGORY_NUM" "CCC") ("BlockName" "Tree-03-10-CC10"))
(("00_CATEGORY_NUM" "CCC") ("BlockName" "Tree-03-06-CC12"))
(("00_CATEGORY_NUM" "AAA") ("BlockName" "Tree-02-08-QS3"))
(("00_CATEGORY_NUM" "AAA") ("BlockName" "Tree-02-22-QS13"))
(("00_CATEGORY_NUM" "AAA") ("BlockName" "Tree-02-20-QS10"))
(("00_CATEGORY_NUM" "GGG") ("BlockName" "Shrub-36-07-LPC"))
(("00_CATEGORY_NUM" "GGG") ("BlockName" "Shrub-36-01-LP"))
(("00_CATEGORY_NUM" "GGG") ("BlockName" "Shrub-36-03-L"))
(("00_CATEGORY_NUM" "GGG") ("BlockName" "Shrub-30-07-KL"))
(("00_CATEGORY_NUM" "GGG") ("BlockName" "Shrub-30-12-UP"))
(("00_CATEGORY_NUM" "GGG") ("BlockName" "Shrub-30-08-TY"))
(("00_CATEGORY_NUM" "HHH") ("BlockName" "Hatch-16-ATR@10"))
(("00_CATEGORY_NUM" "HHH") ("BlockName" "Hatch-01-ZZ@4"))
(("00_CATEGORY_NUM" "HHH") ("BlockName" "Hatch-05-AA@12"))
); list
); setq

(setq test
(vl-sort BlockDataBase
'(lambda (A B)
(cond
(
(< (nth 1 (nth 0 A)) (nth 1 (nth 0 B)))
)
(
(= (nth 1 (nth 0 A)) (nth 1 (nth 0 B)))
(< (nth 1 (nth 1 A)) (nth 1 (nth 1 B)))
(<
(substr (nth 1 (nth 1 A)) (+ (vl-string-position (ascii "-") (nth 1 (nth 1 A)) 0 T) 2))
(substr (nth 1 (nth 1 B)) (+ (vl-string-position (ascii "-") (nth 1 (nth 1 B)) 0 T) 2))
)
(<
(strlen (nth 1 (nth 1 A)))
(strlen (nth 1 (nth 1 B)))
)
)
)
)
)
)

Message 13 of 15


@Redraiderr2009 wrote:

 

... I need a way to get them to sort according to size. Please see the attached image to see the results of this sort. I basicially need "Tree-02-20-QS10" to come before "Tree-02-08-QS3". The sort will put the 1 (in ten) ahead of the 3 even though the 10 is a higher number than 3. I am thinking that if one has more charcters than the other then it should sort the list by character length. ....


Are the components following the last hyphen always two letters followed by numbers?  If so, it shouldn't be very hard to adjust the starting position of the (substr) to bypass those and just look at the numerical characters following.  Even if it may not always be two, it's possible to have any characters you list trimmed off, so you could designate the whole alphabet to eliminate one, or three, or however many, alphabetic characters.  But it would involve converting the remaining numerical characters into integers and comparing those values numerically, not as text strings alphabetically.

Kent Cooper, AIA
Message 14 of 15

I can say there are a minimum of two characters and then a size which can be 1 or 2 digit numbers. (Now watch .... someone will invent a reason to have a three digit number :smileyfrustrated :). I would say that a three digiit number would be abnormal. 

 

Some examples of the data that I deal with.

***Tree blocks usually have two to three characters and have one to two digits

Tree-02-20-QV3

Tree-02-20-QV6

Tree-02-20-QV10

Tree-02-20-QV12

Tree-04-08-LIN8

Tree-04-10-LIN10

Tree-04-12-LIN12

*** Shrub blocks usually have no numbers but there are times for it

Shrub-36-02-IVN

Shrub-30-04-TA5

Shrub-36-08-TA10

**Hatch blocks are usually inserted with the hatch to provide the information needed.

Hatch-01-LM@12

Hatch-04-AA@8

Hatch-02-LMM@10

Hatch-11-ROPP@24

 

Message 15 of 15

perhaps there is not this problem in your case but if you have to sort other kinds of lists
please note that vl-sort can delete elements that it sees as equal (maybe only if atoms):

(vl-sort
 '(3 2 1 3 1 2 2 2)
 '<
)
=> (1 2 3)

;  original by tony tanzillo (please note
;  the name change, i.e. "_" instead of "-")
(defun VL_Sort ( lst func )
      (mapcar
        '(lambda (x) (nth x lst))
         (vl-sort-i lst func)
      )
)

(VL_Sort
 '(3 2 1 3 1 2 2 2)
 '<
)
=> (1 1 2 2 2 2 3 3)

 

Marc'Antonio Alessi

http://alessi.xoom.it//alessi/Programmi.htm
(vl-string-translate "1234567890" "ie@mst.lan" "499825513610716")
2D Parametric for 2000-2013

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk Design & Make Report

”Boost