subst 3rd value in list based on matching 1st and 2nd value

subst 3rd value in list based on matching 1st and 2nd value

Anonymous
Not applicable
1,094 Views
9 Replies
Message 1 of 10

subst 3rd value in list based on matching 1st and 2nd value

Anonymous
Not applicable

I am trying to create a list of blocks in the drawing.

The multiple blocks can be on multiple layers or 1 layer.

 

I can't get past updating the quantity of the blocks if it already exists in the list.

 

Example list where I run into problems -

(setq List_Blocks (list (list "Block1" "LayerA" 1) (list "Block1" "LayerB" 1) (list "Block2" "LayerA" 1)))

 

How do I find and update the 3rd value (quantity) for "Block1" "LayerB" without having to step thru the list each time?

 

 

0 Likes
1,095 Views
9 Replies
Replies (9)
Message 2 of 10

SeeMSixty7
Advisor
Advisor

As long as you are dealing with unique lists you can use

(subst newitem olditem lst)

 

(setq old (nth 2 List_Blocks))

(setq new (list "Block2" "MYNEWLAYER" 3))

(setq NewList (subst new old List_Blocks))

 

If your list is not going to be unique, you can simply rebuild the list around the new value.

 

 

hope that helps,

 

0 Likes
Message 3 of 10

Anonymous
Not applicable

The list is not static and the problem is I don't know in which position the block/layer is located.

I am trying to efficiently find a specific Block/Layer in the list and update the 3rd item (quantity) without having to step thru the list each time I have to update the list.

 

0 Likes
Message 4 of 10

SeeMSixty7
Advisor
Advisor

Sorry I misunderstood the first post.

 

Stepping through the list works just fine. Just create a function routine to do the substitutions and then pass it the list and the new value.

 

If you can reconfigure yours list you may find it easier to organize the data.

 

(setq list_blocks

(list (list "BlockA" (list "Layer1" 1)

                           (list "Layer2" 3)

      )

      (list "BlockB" (list "Layer1" 3)

                          (list "Layer3" 1)

                          (list "Layer4" 5)

      )

)

)

 

This way you can pull out the Whole section and then easily replace that one group if needed. This would limit you to just cycling through the section for the one block.

 

Let me know if this makes sense.

 

Good luck

 

 

 

0 Likes
Message 5 of 10

Ranjit_Singh
Advisor
Advisor

I think you need to do some kind of iteration. There is no in-built function to count duplicate string elements.

Spoiler
(defun c:blocklst ( / testlst current ss1)
(setq 	ss1 (ssget "_x" '((0 . "INSERT")))
	testlst (mapcar '(lambda (x) (strcat (cdr (assoc 2 (setq current (entget (cadr x))))) " " (cdr (assoc 8 current)))) (ssnamex ss1))
	testlst (mapcar '(lambda (x) (cons x (- (length testlst) (length (setq testlst (vl-remove nil (subst nil x testlst))))))) testlst))
(vl-remove nil (mapcar '(lambda (x) (if (/= 0 (cdr x)) x)) testlst))
)

 

 

0 Likes
Message 6 of 10

stevor
Collaborator
Collaborator

 

Did a similar process, by rearranging the data list,

to use the 'assoc  function:

 


(setq BDLL (list (list "Block1" "LayerA" 1)
                         (list "Block1" "LayerB" 1)
                         (list "Block2" "LayerA" 1) )  ) ; s

(setq BDT (mapcar (function (lambda ( D )
           (list (list (car D) (cadr D)) (caddr D)) ))  BDLL) ) ; s

(textscr)
(setq BDX (list "Block1" "LayerB" )   ; IS
         BDY (list "Block2" "LayerB" ) ) ; IS NOT

(if (setq FAL (assoc BDX BDT )) ; Found Assoc
  (setq ; NA (Cadr FAL) ; not req'd
            NAL (List BDX (1+ (cadr FAL)) ) ; New
            BDT (subst NAL FAL BDT ) ) ; Subst'd
   (princ"\n Nyet. ")
) ; if'd

 

BDT: ((("Block1" "LayerA") 1) (("Block1" "LayerB") 2) (("Block2" "LayerA") 1))

 

To rearrange the data list, another mapcar.

 

 

S
0 Likes
Message 7 of 10

stevor
Collaborator
Collaborator

And the rearrangement to the initial order:

 

(setq BDLL (mapcar (function (lambda ( D )
    (setq A (car D) B (cadr D)) (pnl_"A B")
    (list (car A) (cadr A) B ) ) ) BDT ) ) ; S

 

BDLL: (("Block1" "LayerA" 1) ("Block1" "LayerB" 2) ("Block2" "LayerA" 1))

S
0 Likes
Message 8 of 10

martti.halminen
Collaborator
Collaborator

@Anonymous wrote:

I am trying to create a list of blocks in the drawing.

The multiple blocks can be on multiple layers or 1 layer.

 

I can't get past updating the quantity of the blocks if it already exists in the list.

 

Example list where I run into problems -

(setq List_Blocks (list (list "Block1" "LayerA" 1) (list "Block1" "LayerB" 1) (list "Block2" "LayerA" 1)))

 

How do I find and update the 3rd value (quantity) for "Block1" "LayerB" without having to step thru the list each time?

 

 


As a list is only accessible via a pointer to its first item, you'll always have to step through (part) of the list; either explicitly or in the internals of ASSOC, MEMBER etc.

 

As AutoLISP lacks the tools to directly modify list items (unlike other Lisps, where you could use SETF, RPLACD etc. for this), you can only modify a list by either copying it, changing something during the copy, or by adding new stuff in front.

 

The copying variant is done for example by SUBST.

 

The idea with adding new stuff in front is to just find the first occurrence of a key, ignoring the older items with the same key. The idea is known as an association list, so the operator is ASSOC.

 

One way of doing this:

 

(defun reset-blockcount ()
  (setq *blocks* nil
        *keys* nil))

(defun increment-blockcount (name layer / key count)
  (setq key (cons name layer))
  (cond ((member key *keys*)
         ;; Already on the list, increment count
         (setq count (cadr (assoc key *blocks*)))
         (setq *blocks*
               (cons (list key (1+ count)) *blocks*)))
        (T ;; not previously seen, create item
         (setq *keys* (cons key *keys*))
         (setq *blocks*
               (cons (list key 1) *blocks*)))))

(defun show-blockcount()
  (mapcar (function
           (lambda (key)
            (list (car key)
                  (cdr key)
                  (cadr (assoc key *blocks*)))))
          *keys*))

_$ (reset-blockcount)
nil
_$ (increment-blockcount "Block1" "LayerA")
((("Block1" . "LayerA") 1))
_$ (increment-blockcount "Block1" "LayerB")
((("Block1" . "LayerB") 1) (("Block1" . "LayerA") 1))
_$ (increment-blockcount "Block2" "LayerA")
((("Block2" . "LayerA") 1) (("Block1" . "LayerB") 1) (("Block1" . "LayerA") 1))

_$ (increment-blockcount "Block1" "LayerB")
((("Block1" . "LayerB") 2) (("Block2" . "LayerA") 1) (("Block1" . "LayerB") 1) (("Block1" . "LayerA") 1))
_$ (increment-blockcount "Block1" "LayerA")
((("Block1" . "LayerA") 2) (("Block1" . "LayerB") 2) (("Block2" . "LayerA") 1) (("Block1" . "LayerB") 1) (("Block1" . "LayerA") 1))

 

_$ (show-blockcount)
(("Block2" "LayerA" 1) ("Block1" "LayerB" 2) ("Block1" "LayerA" 2))

 

 

-

 

0 Likes
Message 9 of 10

Anonymous
Not applicable

Just to let everyone know. I ended up strcat the Block and Layer names separated by a ~. Then I can use assoc and subst to increment the count. After totaling the blocks, I create a new list with the Block/Layer text separated.

Brute force works again, just not elegantly.

0 Likes
Message 10 of 10

john.uhden
Mentor
Mentor
That's a clever way to defuse your explosives. I was working on a multi mapcar technique just to satisfy you, but it wasn't at all elegant. Sure wish I had better things to do.

John F. Uhden

0 Likes