如何解决将存储在 S 表达式中的数据读入另一个 Common Lisp 程序的内存中 读取文件访问卡片制作抽认卡结构警告结论
我有一个 Common Lisp 程序,可以从 S 表达式文件中读取数据。最初该程序打算用 C 语言编写,并带有一个 CSV 文件,该文件被解析为内存中的结构,但我切换到 Common Lisp 和 S 表达式以消除程序员和用户之间的抽象。如果我在 program.lisp
中定义了一个结构:
(defstruct flashcard
front
back
)
(:virology
(
(:card
:front "To what sort of cells does Epstain-Barr virus attach?"
:back "B-cells"
)
(:card
:front "For how long does the virus of herpes simplex persist in tissues?"
:back "lifetime"
)
(:card
:front "What T-cell receptors are recognised by HIV?"
:back CD4
)
)
...里面有几个列表,很像 ":virology"。
我如何将 data.lisp
读入内存以便可以访问每张卡
来自program.lisp
。
对于上下文,我希望根据用户提供的过滤器(即它们列出:病毒学:生物化学:体内平衡等)迭代队列中的每张卡片,并在卡片上对用户进行测验。不过,我看不出这个特定用例会如何影响我将文件加载到内存中的方式。
(注意:就目前而言,只有两个字段的简单结构更有可能更适合 CSV 文件,但是,当我开发程序时,我打算添加元数据等。因此将数据存储在 Lisp 中从长远来看,由 program.lisp
读取的 S 表达式更有用)
解决方法
读取文件
假设你在 data.lisp
所在的目录中启动你的 Lisp 程序(例如 sbcl、ecl),那么这应该会产生一个值树(这里的 *
是 REPL 中的提示,什么以下是正在读取的值):
* (with-open-file (in "data.lisp")
(read in))
(:VIROLOGY
((:CARD :FRONT "To what sort of cells does Epstain-Barr virus attach?" :BACK
"B-cells")
(:CARD :FRONT
"For how long does the virus of herpes simplex persist in tissues?" :BACK
"lifetime")
(:CARD :FRONT "What T-cell receptors are recognised by HIV?" :BACK CD4)))
通常你会把它包装在一个函数中:
(defun cards (&optional (file "data.lisp"))
(with-open-file (in file) (read in)))
假设您有多张卡片,请按如下方式编写:
(:virology (...) :biology (....) :physics (...))
如果改为编写多个列表,如下所示:
(:virology (...))
(:biology (...))
(:physics (...))
那么上面的cards
需要循环:
(defun cards (&optional (file "data.lisp"))
(with-open-file (in file)
(loop
for item = (read in nil in)
until (eq item in)
collect item)))
上面有一个技巧,因为你想收集值直到没有更多值,但你不想在文件结束时抛出异常。这就是为什么 read
将 nil
作为第二个参数(没有错误),第三个参数是它在到达文件结尾时应该返回的值。这里的值是流对象 in
本身:它用于拥有一个不可能由 read
生成的唯一值(与 nil
不同)。在您的情况下,这并不是真正必要的,您可以使用 nil
代替,但总的来说这是一个很好的做法。
访问卡片
如果您选择一种或另一种方式,则必须适应查询您的值。假设您使用上面的 loop
,因此您的数据是单个条目列表,由循环收集:
((:virology (...))
(:biology (...))
(:physics (...)))
这就像一个关联列表,其中每个元素都是一个 cons-cell,这样 car
是一个键,而 cdr
是一个值。如果您拨打 (assoc :virology (cards))
,您将:
(:VIROLOGY
((:CARD :FRONT "To what sort of cells does Epstain-Barr virus attach?" :BACK
"B-cells")
(:CARD :FRONT
"For how long does the virus of herpes simplex persist in tissues?" :BACK
"lifetime")
(:CARD :FRONT "What T-cell receptors are recognised by HIV?" :BACK CD4)))
其中的 cdr
是一个具有单个值的列表,即卡片列表。
您可以简化数据格式,以便使用此格式:
(:virology (:card ...) (:card ...) (:card ...))
代替:
(:virology ((:card ...) (:card ...) (:card ...)))
这会删除一层嵌套,而不是包含一张卡片列表的列表,您可以直接访问卡片列表作为条目的 cdr
。因此,假设您编辑 data.lisp
以删除一层嵌套,然后:
(defun find-cards (cards key)
(cdr (assoc key cards)))
* (find-cards (cards) :virology)
((:CARD :FRONT "To what sort of cells does Epstain-Barr virus attach?" :BACK
"B-cells")
(:CARD :FRONT
"For how long does the virus of herpes simplex persist in tissues?" :BACK
"lifetime")
(:CARD :FRONT "What T-cell receptors are recognised by HIV?" :BACK CD4))
到目前为止,还不错。
概括地说,我们有一个关联列表,将键映射到卡片列表。
如果您想改用哈希表,那么您可以自己填充一个,或者使用像 alexandria 这样的库。为此,您可能应该首先设置 Quicklisp:
* (ql:quickload :alexandria)
To load "alexandria":
Load 1 ASDF system:
alexandria
; Loading "alexandria"
(:ALEXANDRIA)
然后,您可以调用:
* (alexandria:alist-hash-table (cards))
#<HASH-TABLE :TEST EQL :COUNT 1 {1015268CE3}>
如果您到了这一步,您可以在 Peter 的 Seibel Practical Common Lisp 的第 11. Collections 章中查看哈希表的工作原理。
制作抽认卡结构
在任何情况下,无论您使用 find-cards
还是使用 GETHASH
访问卡片,您都会获得特定格式的卡片列表。如果要将它们转换为结构的实例,那么首先需要定义一种将卡片从列表格式转换为结构的方法。
每张卡片都存储在一个以 :card
开头的列表中,其余的是一个属性列表值。属性列表是一连串的键和值,以扁平的方式:
(:a 0 :b 1 :c 2)
幸运的是,您可以使用 DESTRUCTURING-BIND
来匹配已知格式(对于更复杂的格式,有模式匹配库):
(defun parse-card (list)
;; This is the expected format,the list starts with `:card`,so
;; I add an assertion here.
(assert (eq :card (first list)))
;; The rest of the list is a property list,let's bind front and back
;; to the values associated with keys :front and :back
(destructuring-bind (&key front back) (rest list)
;; this is a function generated by "defstruct"
(make-flashcard :front front :back back)))
例如:
* (parse-card '(:card :front 0 :back 1))
#S(FLASHCARD :FRONT 0 :BACK 1)
完成这项工作后,您可以使用 mapcar 来转换卡片列表:
* (mapcar #'parse-card (find-cards (cards) :virology))
(#S(FLASHCARD
:FRONT "To what sort of cells does Epstain-Barr virus attach?"
:BACK "B-cells")
#S(FLASHCARD
:FRONT "For how long does the virus of herpes simplex persist in tissues?"
:BACK "lifetime")
#S(FLASHCARD :FRONT "What T-cell receptors are recognised by HIV?" :BACK CD4))
我知道结构很容易定义,但它们在实时系统中不容易改变:如果你想添加一个新的槽,那么你需要重新启动你的 Lisp(这是为了高效编译,就像在静态类型语言,而类更动态)。
警告
当你读取的数据是一个符号时,比如CD4
,它会属于当前调用read
时绑定的包。这可能会污染您的包裹和/或造成困难。您可能更喜欢使用字符串。
结论
这不是一个完整的解决方案,但您现在应该有不同的工具来取得进展,具体取决于您想去的地方。
,要从文件中读取 lisp 表单,请参阅 uiop:read-file-form[s]
。单数读取1个结构(就像一个巨大的alist),复数读取文件中的多个结构。
如果您想将程序中的数据保存到文件中,您可以简单地编写它们,例如使用 format <file> "~S"
(~S
保持结构,而不是 ~A
),但是使用一些预防措施:
(let ((*print-pretty* nil) ;;
(*print-length* nil)) ;; don't abbreviate long lines with "..."
… write to file …)
UIOP 也有 read-file-line[s]
和 read-file-string
。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。