0

Question: The code is below, the contents of the list myList were copied in the debugger.

(let* ((myList '(("Concept Index"
                  ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi")
                  ("ТочкаНоды" "@node Concept Index")
                  ("спсСсылок"))
                 ("Function Index"
                  ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi")
                  ("ТочкаНоды" "@node Function Index")
                  ("спсСсылок"))
                 ("Variable Index"
                  ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi")
                  ("ТочкаНоды" "@node Variable Index")
                  ("спсСсылок"))
                 ("Reserved Word Index"
                  ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi")
                  ("ТочкаНоды" "@node Reserved Word Index")
                  ("спсСсылок"))
                 ("Builtin Index"
                  ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi")
                  ("ТочкаНоды" "@node Builtin Index")
                  ("спсСсылок"))
                 ("Indexes"
                  ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi")
                  ("ТочкаНоды" "@node Indexes")
                  ("спсСсылок"))
                 ("GNU Free Documentation License"
                  ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi")
                  ("ТочкаНоды" "@node GNU Free Documentation License")
                  ("спсСсылок"))
                 ("Major Differences From The Bourne Shell"
                  ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi")
                  ("ТочкаНоды" "@node Major Differences From The Bourne Shell")
                  ("спсСсылок"))
                 ("Reporting Bugs"
                  ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi")
                  ("ТочкаНоды" "@node Reporting Bugs")
                  ("спсСсылок"))
                 ("Optional Features"
                  ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi")
                  ("ТочкаНоды" "@node Optional Features")
                  ("спсСсылок"))))
       (текАссоц (assoc "спсСсылок" (assoc "Optional Features" myList)))
       (текСписСсылок (cdr текАссоц)))
  (setcdr текАссоц (add-to-list 'текСписСсылок "hudoSsulka"))
  myList)

The code fulfills as expected: in the associative list with the association "Optional Features", in the associative list with the association

"спсСсылок"

the element

"hudoSsulka"

is added.

However, with the following code:

(let* ((myList спЯкрНд)
       (текАссоц (assoc "спсСсылок" (assoc (match-string-no-properties 1) myList)))
       (текСпсСсылок (cdr текАссоц)))
  (setcdr текАссоц (add-to-list (quote текСпсСсылок) (match-string-no-properties 0)))
  myList)

all indiscriminately the parent of the

"спсСсылок"

association is populated. Why does this happen? what happens with the list when changing one specific nested list

(("Function Index" ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi") ("ТочкаНоды" "@node Function Index") ("спсСсылок" "@ref{Bash Features}")) ("Variable Index" ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi") ("ТочкаНоды" "@node Variable Index") ("спсСсылок" "@ref{Bash Features}")) ("Reserved Word Index" ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi") ("ТочкаНоды" "@node Reserved Word Index") ("спсСсылок" "@ref{Bash Features}")) ("Builtin Index" ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi") ("ТочкаНоды" "@node Builtin Index") ("спсСсылок" "@ref{Bash Features}")) ("Indexes" ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi") ("ТочкаНоды" "@node Indexes") ("спсСсылок" "@ref{Bash Features}")) ("GNU Free Documentation License" ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi") ("ТочкаНоды" "@node GNU Free Documentation License") ("спсСсылок" "@ref{Bash Features}")) ("Major Differences From The Bourne Shell" ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi") ("ТочкаНоды" "@node Major Differences From The Bourne Shell") ("спсСсылок" "@ref{Bash Features}")) ("Reporting Bugs" ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi") ("ТочкаНоды" "@node Reporting Bugs") ("спсСсылок" "@ref{Bash Features}")) ("Optional Features" ("Файл" "/home/alamd/example/funEl/expirement/perevod_bash6/bash.texi") ("ТочкаНоды" "@node Optional Features") ("спсСсылок" "@ref{Bash Features}")))

Please look at the overall code:

;;;ПлчСпсЯкр функция возвращает список якорей файлов  
(defun ПлчСпсЯкр (списокФайлов)
;;;спсЯкр переменная для списка якорей файлов в спФйл
  (let (
    (списокЯкорей (list));;;ассоциативный список нод и якорей
    (списокОглавления (list))
    (спсОбщий (list))
    ткБфр)
    (progn
;;;Перебираем список с путями к файлам
      (dolist (текИмяФайла списокФайлов списокЯкорей)
;;;Создаем буфер и сохраняем его имя
    (setq ткБфр (find-file-noselect текИмяФайла))
;;;Переключаемся в созданный буфер
    (switch-to-buffer ткБфр)
;;;Чешем в начало буфера
    (goto-char (point-min))
;;;Ищем якоря и добавляем их в список
    (while (and (< (point) (point-max))
            (re-search-forward 
;;;Ищем ноду или якорь, добавляем в список
             "^\\(?2:@node[[:space:]]+\\(?1:.+\\),?.*\\)\\|\\(?2:@anchor{\\(?1:[^}]+\\)}+?\\)" (point-max) t)
            (let ((line (match-string-no-properties 2)) (soderjanie (match-string-no-properties 1)))
              (add-to-list 'списокЯкорей `(,soderjanie ("Файл" ,текИмяФайла) ("ТочкаНоды" ,line) ("спсСсылок")))))
      )
      ;;;Ищем команды структурирования
    (goto-char (point-min))
    (while (and (< (point) (point-max))
            (re-search-forward 
;;;Ищем ноду или якорь, добавляем в список
             "\\(@\\(?:appendix\\(?:s\\(?:ec\\(?:tion\\)?\\|ubs\\(?:\\(?:ubs\\)?ec\\)\\)\\)?\\|chap\\(?:heading\\|ter\\)\\|heading\\|majorheading\\|s\\(?:ection\\|ub\\(?:heading\\|s\\(?:ection\\|ub\\(?:heading\\|section\\)\\)\\)\\)\\|top\\|unnumbered\\(?:s\\(?:\\(?:ubs\\(?:ubs\\)?\\)?ec\\)\\)?\\)\\)\\>.+" (point-max) t)
            (add-to-list 'списокОглавления (match-string-no-properties 0)))
      )
;;;Удаляем обработанный буфер
    (kill-buffer ткБфр)
    )
      (add-to-list 'спсОбщий (cons "Якоря" списокЯкорей))
      (add-to-list 'спсОбщий (cons "Содержание" списокОглавления))
      спсОбщий)
    )
  )

Part of the code when creating the list

(let ((line (match-string-no-properties 2)) (soderjanie (match-string-no-properties 1)))
              (add-to-list 'списокЯкорей `(,soderjanie ("Файл" ,текИмяФайла) ("ТочкаНоды" ,line) ("спсСсылок"))))

or should I rewrite the list overflow code?

6
  • Welcome to emacs.se. Please clarify your question. Please also edit the title of your post to give it an informative title.
    – Dan
    Commented Nov 28, 2019 at 17:39
  • As stated, your question is not reproducible or entirely clear, but I strongly suspect that you may be encountering issues due to modifying a quoted list. See emacs.stackexchange.com/q/51749/15748, emacs.stackexchange.com/q/20535/15748, and emacs.stackexchange.com/q/45814/15748. Never call setcdr directly on a quoted list, i.e. one created as '(...). Instead, either create the list with list, or first make a copy of it with copy-sequence or similar.
    – Basil
    Commented Nov 28, 2019 at 18:58
  • Thanks for the answer. I reproduced here part of the real list by copying in the debugger. In the code, this list is passed in the parameter.
    – uanr81
    Commented Nov 28, 2019 at 20:11
  • @Basil In principle you are right since Common Lisp can do Common Subexpression Elimination. However, I've never seen this yet in Elisp. This is a gray area in the Elisp manual. They even use and modify the structure quoted lists in the manual. (See my related question here on Emacs.SE.
    – Tobias
    Commented Nov 28, 2019 at 20:47
  • Which version of Emacs do you use? 1. You should not use add-list for let-bound variables. Use cl-pushnew instead. (Mind the order of arguments!) 2. Construct the list with list instead of the backquote.
    – Tobias
    Commented Nov 28, 2019 at 22:28

1 Answer 1

2

The effect you describe occurs when you use the same sublist for the construction of your alist and not just equal sublists.

The same list remains associated to all the keys of your alist and you can address this same and only sublist through all keys. When you access this list through one of the keys and modify it then this modification is visible through all entries of the alist.

There follows a simplified example. At first we define the SUBLIST. Afterwards we construct an alist with the keys 1, 2 to which we associate the same SUBLIST. Afterwards we modify the list associated to 2 by overwriting a with A.

(let* ((SUBLIST '(a b))
       (alist (mapcar (lambda (x)
               (cons x SUBLIST))
             '(1 2))))
  (setcar (cdr (assoc 2 alist)) 'A)
  alist)

The result is:

((1 A b) (2 A b))

We see the same modified sublist (A b) associated to both keys 1 and 2.

The cure to your problem is simple: Don't associate the same list to all keys but only copies of it:

(let* ((SUBLIST '(a b))
       (alist (mapcar (lambda (x)
               (cons x (seq-copy SUBLIST)))
             '(1 2))))
  (setcar (cdr (assoc 2 alist)) 'A)
  alist)

Now you get the results as you expect them:

((1 a b) (2 A b))

Another warning is in place if you have nested alists:
(setq COPY (seq-copy SUBLIST)) produces a shallow copy of your list SUBLIST. If some car of the conses of SUBLIST contains a list CARLIST then only the link to CARLIST is copied not CARLIST itself. If you actually want copies of CARLIST in COPY you should use cl-copy-tree instead of seq-copy.


A variation of the theme are quoted lists in loops. If you push a quoted list on a list within a loop you just push the same link there multiple times.
This is demonstrated with the next example code:

(let (ret)
  (dotimes (i 3)
    (push (cons i '(a b)) ret))
  (setf (nth 1 (car ret)) 'A)
  ret)

The result shows that 0,1,2 are all associated with the same list. If the value corresponding to key 2 is modified. The new value can be accessed through all keys.

((2 A b) (1 A b) (0 A b))

The following example code demonstrates that back-quotes apparently do not have this property. Albeit, the doc string of backquote actually suggests that if no comma operator is used in the back-quoted list the back-quoted list should act like a quoted list.

(let (ret)
  (dotimes (i 3)
    (push (cons i `(a b)) ret))
  (setf (nth 1 (car ret)) 'A)
  ret)

Only the value corresponding to the first key is changed:

((2 A b) (1 a b) (0 a b))

For completeness the doc string of backquote:

The whole structure acts as if it were quoted except for certain places where expressions are evaluated and inserted or spliced in.

For example:

b => (ba bb bc) ; assume b has this value
`(a b c) => (a b c) ; backquote acts like quote
`(a ,b c) => (a (ba bb bc) c) ; insert the value of b
`(a ,@b c) => (a ba bb bc c) ; splice in the value of b

Vectors work just like lists. Nested backquotes are permitted.


Now some words about your example. I have rewritten your code as self-contained example. No need of external files:

(defun get-anchor-list ()
  "Demonstrate the effects of https://emacs.stackexchange.com/questions/54050/unexpected-behavior-when-updating-alist."
  (let* ((file-list '(("file1.info" "@chapter{One11}This marks the @anchor{x-spot12}spot.\nThis marks the @anchor{x-spot13}spot.@section{Second}@node{lastNode14}")
              ("file2.info" "@chapter{One21}This marks the @anchor{x-spot22}spot.\nThis marks the @anchor{x-spot23}spot.@section{Second}@node{lastNode24}")))
     (anchor-list (list))
     (toc (list))
     (overall-list (list))
     tex-buffer
     info-file-name)
    (dolist (info-file file-list)
      (setq info-file-name (car info-file))
      (with-temp-buffer
    (insert (cadr info-file))
    (goto-char (point-min))
    (while (and (< (point) (point-max))
            (re-search-forward 
             "^\\(?2:@node[[:space:]]+\\(?1:.+\\),?.*\\)\\|\\(?2:@anchor{\\(?1:[^}]+\\)}+?\\)" (point-max) t)
            (let ((line (match-string-no-properties 2))
              (soderjanie (match-string-no-properties 1)))
              (add-to-list 'anchor-list `(,soderjanie ("File" ,info-file-name) ("Node Point" ,line) ("Reference")))))
      )
    (goto-char (point-min))
    (while (and (< (point) (point-max))
            (re-search-forward 
             "\\(@\\(?:appendix\\(?:s\\(?:ec\\(?:tion\\)?\\|ubs\\(?:\\(?:ubs\\)?ec\\)\\)\\)?\\|chap\\(?:heading\\|ter\\)\\|heading\\|majorheading\\|s\\(?:ection\\|ub\\(?:heading\\|s\\(?:ection\\|ub\\(?:heading\\|section\\)\\)\\)\\)\\|top\\|unnumbered\\(?:s\\(?:\\(?:ubs\\(?:ubs\\)?\\)?ec\\)\\)?\\)\\)\\>.+" (point-max) t)
            (add-to-list 'toc (match-string-no-properties 0))))
    ))
    (add-to-list 'overall-list (cons "Anchor" anchor-list))
    (add-to-list 'overall-list (cons "Content" toc))
    overall-list))

(get-anchor-list)

If you instrument get-anchor-list with edebug and step forwards up to the last occurence of overall-list you get:

(("Content" "@section{Second}@node{lastNode24}" "@chapter{One21}This marks the @anchor{x-spot22}spot." "@section{Second}@node{lastNode14}" "@chapter{One11}This marks the @anchor{x-spot12}spot.")
("Anchor" ("x-spot23" (#1="File" #2="file2.info")
(#3="Node Point" "@anchor{x-spot23}") . #4=(("Reference")))
("x-spot22" (#1# #2#)
(#3# "@anchor{x-spot22}") . #4#)
("x-spot13" (#1# #5="file1.info")
(#3# "@anchor{x-spot13}") . #4#)
("x-spot12" (#1# #5#)
(#3# "@anchor{x-spot12}") . #4#)))

The most interesting part is that Elisp takes the list #4=(("Reference")) and re-uses it in the successive lists. These are references to the same list. If you modify the list #4 through on of its links you see this modification through all links.

You can avoid this effect by generating new lists from scratch with list and cons. There follows the modified code:

(defun get-anchor-list ()
  "Demonstrate the effects of https://emacs.stackexchange.com/questions/54050/unexpected-behavior-when-updating-alist."
  (let* ((file-list '(("file1.info" "@chapter{One11}This marks the @anchor{x-spot12}spot.\nThis marks the @anchor{x-spot13}spot.@section{Second}@node{lastNode14}")
              ("file2.info" "@chapter{One21}This marks the @anchor{x-spot22}spot.\nThis marks the @anchor{x-spot23}spot.@section{Second}@node{lastNode24}")))
     (anchor-list (list))
     (toc (list))
     (overall-list (list))
     tex-buffer
     info-file-name)
    (dolist (info-file file-list)
      (setq info-file-name (car info-file))
      (with-temp-buffer
    (insert (cadr info-file))
    (goto-char (point-min))
    (while (and (< (point) (point-max))
            (re-search-forward 
             "^\\(?2:@node[[:space:]]+\\(?1:.+\\),?.*\\)\\|\\(?2:@anchor{\\(?1:[^}]+\\)}+?\\)" (point-max) t)
            (let ((line (match-string-no-properties 2))
              (soderjanie (match-string-no-properties 1)))
              (cl-pushnew  (list soderjanie
                     (list "File" info-file-name)
                     (list "Node Point" line)
                     (list "Reference"))
                   anchor-list)))
      )
    (goto-char (point-min))
    (while (and (< (point) (point-max))
            (re-search-forward 
             "\\(@\\(?:appendix\\(?:s\\(?:ec\\(?:tion\\)?\\|ubs\\(?:\\(?:ubs\\)?ec\\)\\)\\)?\\|chap\\(?:heading\\|ter\\)\\|heading\\|majorheading\\|s\\(?:ection\\|ub\\(?:heading\\|s\\(?:ection\\|ub\\(?:heading\\|section\\)\\)\\)\\)\\|top\\|unnumbered\\(?:s\\(?:\\(?:ubs\\(?:ubs\\)?\\)?ec\\)\\)?\\)\\)\\>.+" (point-max) t)
            (cl-pushnew (match-string-no-properties 0) toc)))
    ))
    (add-to-list 'overall-list (cons "Anchor" anchor-list))
    (add-to-list 'overall-list (cons "Content" toc))
    overall-list))

(get-anchor-list)

If you edebug this code you get as value of overall-list:

(("Content" "@section{Second}@node{lastNode24}" "@chapter{One21}This marks the @anchor{x-spot22}spot." "@section{Second}@node{lastNode14}" "@chapter{One11}This marks the @anchor{x-spot12}spot.")
("Anchor" ("x-spot23" (#1="File" #2="file2.info")
(#3="Node Point" "@anchor{x-spot23}")
(#4="Reference"))
("x-spot22" (#1# #2#)
(#3# "@anchor{x-spot22}")
(#4#))
("x-spot13" (#1# #5="file1.info")
(#3# "@anchor{x-spot13}")
(#4#))
("x-spot12" (#1# #5#)
(#3# "@anchor{x-spot12}")
(#4#))))

Contrary to the previous case the string #4="Reference" is reused and no longer structural element of the list.

Alternatively you can deep-copy the tree structure with cl-copy-tree getting the same result:

(defun get-anchor-list ()
  "Demonstrate the effects of https://emacs.stackexchange.com/questions/54050/unexpected-behavior-when-updating-alist."
  (let* ((file-list '(("file1.info" "@chapter{One11}This marks the @anchor{x-spot12}spot.\nThis marks the @anchor{x-spot13}spot.@section{Second}@node{lastNode14}")
              ("file2.info" "@chapter{One21}This marks the @anchor{x-spot22}spot.\nThis marks the @anchor{x-spot23}spot.@section{Second}@node{lastNode24}")))
     (anchor-list (list))
     (toc (list))
     (overall-list (list))
     tex-buffer
     info-file-name)
    (dolist (info-file file-list)
      (setq info-file-name (car info-file))
      (with-temp-buffer
    (insert (cadr info-file))
    (goto-char (point-min))
    (while (and (< (point) (point-max))
            (re-search-forward 
             "^\\(?2:@node[[:space:]]+\\(?1:.+\\),?.*\\)\\|\\(?2:@anchor{\\(?1:[^}]+\\)}+?\\)" (point-max) t)
            (let ((line (match-string-no-properties 2))
              (soderjanie (match-string-no-properties 1)))
              (cl-pushnew  (cl-copy-tree `(,soderjanie ("File" ,info-file-name) ("Node Point" ,line) ("Reference")))
                   anchor-list)))
      )
    (goto-char (point-min))
    (while (and (< (point) (point-max))
            (re-search-forward
             "\\(@\\(?:appendix\\(?:s\\(?:ec\\(?:tion\\)?\\|ubs\\(?:\\(?:ubs\\)?ec\\)\\)\\)?\\|chap\\(?:heading\\|ter\\)\\|heading\\|majorheading\\|s\\(?:ection\\|ub\\(?:heading\\|s\\(?:ection\\|ub\\(?:heading\\|section\\)\\)\\)\\)\\|top\\|unnumbered\\(?:s\\(?:\\(?:ubs\\(?:ubs\\)?\\)?ec\\)\\)?\\)\\)\\>.+" (point-max) t)
            (cl-pushnew (match-string-no-properties 0) toc)))
    ))
    (add-to-list 'overall-list (cons "Anchor" anchor-list))
    (add-to-list 'overall-list (cons "Content" toc))
    overall-list))

(get-anchor-list)
6
  • thanks for the answer. It turns out I did not create the list correctly?
    – uanr81
    Commented Nov 28, 2019 at 20:28
  • @uanr81 Your alist-tree contains ("спсСсылок") several times. That and the effect you are describing leads me to the guess that in the code you have not shown us you actually use the same list as value for several keys. You can change this list through one of the keys but the modified list will be visible through all the keys with the same associated list.
    – Tobias
    Commented Nov 28, 2019 at 20:40
  • @uanr81 I added some words about quoted and back-quoted lists. The problem would be readily explainable with a quoted list. Things are different with back-quoted lists. As the answer explains I cannot reproduce the problem with the simple example using backquotes. But, for an example the constant tail of a back-quoted form could be realized with a quote. Therefore you actually could have the same problems with back-quotes as you have with quotes. The example says that this is not so. But, maybe that changes with byte-compiled code. Did you byte-compile your code?
    – Tobias
    Commented Nov 29, 2019 at 1:22
  • what needs to be changed in my code for creating the initial list, the fact is that the list under the "спсСсылок" key is initially empty
    – uanr81
    Commented Nov 29, 2019 at 1:49
  • @uanr81 Two things: 1) Use cl-pushnew instead of add-to-list (see the doc string of add-to-list). 2) Use (list ...) and (cons ...) instead of the backquote.
    – Tobias
    Commented Nov 29, 2019 at 4:31

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.