在 Lisp 中修改结构 Field/Slot 的副本
Modify A Copy of a Structure's Field/Slot in Lisp
我正在尝试修改结构字段的副本。我尝试使用 COPY-TREE
无济于事。这是我的代码:
(defstruct scenario
(board '() :type list)
(letters "" :type string)
(blank-char #\- :type character))
(defparameter *scen-1*
(make-scenario
:board (string->board "cat---|a-----|b-----" #\|)))
在 SETF
ing 之前:
(print *scen-1*)
#S(SCENARIO
:BOARD ("CAT---" "A-----" "B-----")
:LETTERS ""
:BLANK-CHAR #\-)
当我尝试用 COPY-TREE
修改电路板的副本时,它会修改原始电路板。
(let ((board (copy-tree (scenario-board *scen-1*))))
(setf (subseq (nth 1 board) 0 2) "GG"))
(print *scen-1*)
#S(SCENARIO
:BOARD ("CAT---" "GG----" "B-----")
:LETTERS ""
:BLANK-CHAR #\-)
*scen-1*
应该保持不变。
如何修改 BOARD
字段的副本,而不是原始字段?谢谢!
问题是 COPY-TREE
复制了列表结构,但没有复制列表的元素。这里你真的不需要 COPY-TREE
因为要复制的列表是扁平的。
一个解决方法是编写一个函数,用另一个字符串中的字符替换字符串副本中的字符,然后替换原始 [=15= 副本中的所需 board
元素].这是一个执行子字符串替换的函数:
;;; Writes the characters from NEW to OLD starting from START.
(defun replace-substring (old start new)
(let ((result (copy-seq old)))
(loop for replacement across new
for i from start below (+ start (length new))
do (setf (elt result i) replacement)
finally (return result))))
这是一个函数,它通过更新其中一个字段的 copy 来更新 board
字段的 copy字符串元素:
;;; Creates a copy of the BOARD field with the Nth string replaced
;;; by a copy which has had the characters starting from POS
;;; replaced by the characters from NEW.
(defun update-board (board n pos new)
(let ((new-board (copy-list board))
(new-seq (replace-substring (nth n board) pos new)))
(setf (nth n new-board) new-seq)
new-board))
交互示例:
CL-USER> *scen-1*
#S(SCENARIO :BOARD ("cat---" "a-----" "b-----") :LETTERS "" :BLANK-CHAR #\-)
CL-USER> (update-board (scenario-board *scen-1*) 1 0 "gg")
("cat---" "gg----" "b-----")
CL-USER> *scen-1*
#S(SCENARIO :BOARD ("cat---" "a-----" "b-----") :LETTERS "" :BLANK-CHAR #\-)
作为上述答案的替代方案,我发现 MAPCAR
和 COPY-SEQ
可用于执行字符串列表的“深层复制”。
(let ((board (mapcar #'copy-seq (scenario-board *scen-1*))))
etc
这也有效果。
我正在尝试修改结构字段的副本。我尝试使用 COPY-TREE
无济于事。这是我的代码:
(defstruct scenario
(board '() :type list)
(letters "" :type string)
(blank-char #\- :type character))
(defparameter *scen-1*
(make-scenario
:board (string->board "cat---|a-----|b-----" #\|)))
在 SETF
ing 之前:
(print *scen-1*)
#S(SCENARIO
:BOARD ("CAT---" "A-----" "B-----")
:LETTERS ""
:BLANK-CHAR #\-)
当我尝试用 COPY-TREE
修改电路板的副本时,它会修改原始电路板。
(let ((board (copy-tree (scenario-board *scen-1*))))
(setf (subseq (nth 1 board) 0 2) "GG"))
(print *scen-1*)
#S(SCENARIO
:BOARD ("CAT---" "GG----" "B-----")
:LETTERS ""
:BLANK-CHAR #\-)
*scen-1*
应该保持不变。
如何修改 BOARD
字段的副本,而不是原始字段?谢谢!
问题是 COPY-TREE
复制了列表结构,但没有复制列表的元素。这里你真的不需要 COPY-TREE
因为要复制的列表是扁平的。
一个解决方法是编写一个函数,用另一个字符串中的字符替换字符串副本中的字符,然后替换原始 [=15= 副本中的所需 board
元素].这是一个执行子字符串替换的函数:
;;; Writes the characters from NEW to OLD starting from START.
(defun replace-substring (old start new)
(let ((result (copy-seq old)))
(loop for replacement across new
for i from start below (+ start (length new))
do (setf (elt result i) replacement)
finally (return result))))
这是一个函数,它通过更新其中一个字段的 copy 来更新 board
字段的 copy字符串元素:
;;; Creates a copy of the BOARD field with the Nth string replaced
;;; by a copy which has had the characters starting from POS
;;; replaced by the characters from NEW.
(defun update-board (board n pos new)
(let ((new-board (copy-list board))
(new-seq (replace-substring (nth n board) pos new)))
(setf (nth n new-board) new-seq)
new-board))
交互示例:
CL-USER> *scen-1*
#S(SCENARIO :BOARD ("cat---" "a-----" "b-----") :LETTERS "" :BLANK-CHAR #\-)
CL-USER> (update-board (scenario-board *scen-1*) 1 0 "gg")
("cat---" "gg----" "b-----")
CL-USER> *scen-1*
#S(SCENARIO :BOARD ("cat---" "a-----" "b-----") :LETTERS "" :BLANK-CHAR #\-)
作为上述答案的替代方案,我发现 MAPCAR
和 COPY-SEQ
可用于执行字符串列表的“深层复制”。
(let ((board (mapcar #'copy-seq (scenario-board *scen-1*))))
etc
这也有效果。