分类: 软件

  • 用 Lisp 实现简单数据库的优雅之旅 🛤️

    在编程的世界里,语言的选择如同选择一位舞伴,有的轻盈优雅,有的沉稳可靠,而Lisp无疑是其中的优雅舞者之一。尽管诞生于1958年,Lisp依然在现代编程中大放异彩,正如Paul Graham所言:“Lisp是数学,数学永远不会过时。”今天,我们将通过一个简单的例子,来探讨如何用Lisp实现一个基本的数据库,专门用来存储MP3歌曲的信息。

    CD 和记录 🎵

    首先,我们需要定义我们的数据结构。我们的数据库将包含多条CD记录,每条记录包含以下四个信息:

    • CD标题
    • 艺术家信息
    • 评价信息(满分10分)
    • 是否被烧录(布尔值)

    1. 数据结构的定义

    在Lisp中,我们可以使用列表(list)和属性表(property list,简称plist)作为数据结构。列表类似于Python中的列表,而属性表则更像Python的字典。我们可以用以下代码定义一个CD记录:

    (defun make-cd (title artist rating ripped)
      (list :title title :artist artist :rating rating :ripped ripped))

    使用示例:

    (make-cd "Roses" "Kathy Mattea" 7 t)

    这将返回一个结构化的CD记录:

    (:TITLE "Roses" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T)

    2. 录入 CD 记录 📜

    接下来,我们需要一个地方来存储这些CD记录。我们可以定义一个全局变量*db*(遵循Lisp的命名约定),并利用PUSH宏来添加新的记录:

    (defvar *db* nil)
    
    (defun add-record (cd)
      (push cd *db*))

    现在,我们可以将make-cdadd-record结合起来,方便地将新的CD记录添加到数据库中。

    3. 数据库的格式化输出 🎉

    为了查看数据库中的内容,我们需要一个更友好的输出格式。我们可以使用dolist宏来遍历数据库,并用format函数来格式化输出:

    (defun dump-db ()
      (dolist (cd *db*)
        (format t "~{~a:~10t~a~%~}" cd)))

    调用(dump-db)后,我们将看到如下格式的输出:

    TITLE:    Pork Face
    ARTIST:   Laddy
    RATING:   9
    RIPPED:   T
    TITLE:    Roses
    ARTIST:   Kathy Mattea
    RATING:   7
    RIPPED:   T

    改进用户交互 💬

    使用add-record来添加CD记录显得有些繁琐,因此我们可以编写一个函数来提示用户输入CD信息。以下是一个简单的用户输入函数示例:

    (defun prompt-read (prompt)
      (format *query-io* "~a: " prompt)
      (force-output *query-io*)
      (read-line *query-io*))

    结合prompt-readmake-cd,我们可以创建一个更友好的用户接口:

    (defun prompt-for-cd ()
      (make-cd
       (prompt-read "Title")
       (prompt-read "Artist")
       (or (parse-integer (prompt-read "Rating") :junk-allowed t) 0)
       (y-or-n-p "Ripped [y/n]: ")))

    保存和加载数据库 💾

    为了防止数据丢失,我们可以将数据库保存到文件中,并能够在下次加载时读取。以下是保存和加载的代码示例:

    (defun save-db (filename)
      (with-open-file (out filename :direction :output :if-exists :supersede)
        (with-standard-io-syntax
          (print *db* out))))
    
    (defun load-db (filename)
      (with-open-file (in filename)
        (with-standard-io-syntax
          (setf *db* (read in)))))

    查询数据库 🔍

    有了数据库,我们当然需要查询的功能。我们可以使用remove-if-not函数来筛选出符合条件的记录:

    (defun select (selector-fn)
      (remove-if-not selector-fn *db*))
    
    (defun artist-selector (artist)
      #'(lambda (cd) (equal (getf cd :artist) artist)))

    调用示例:

    (select (artist-selector "Kathy Mattea"))

    更新和删除记录 ✂️

    为了使数据库更加灵活,我们还可以添加更新和删除的功能。这可以通过mapcarremove-if等函数实现:

    (defun update (selector-fn &key title artist rating (ripped nil ripped-p))
      (setf *db*
            (mapcar
             #'(lambda (row)
                 (when (funcall selector-fn row)
                   (if title (setf (getf row :title) title))
                   (if artist (setf (getf row :artist) artist))
                   (if rating (setf (getf row :rating) rating))
                   (if ripped-p (setf (getf row :ripped) ripped)))
                 row) *db*)))
    
    (defun delete-rows (selector-fn)
      (setf *db* (remove-if selector-fn *db*)))
    

    update 函数的逐行解析

    这段代码定义了一个 update 函数,用于更新数据库 *db* 中符合条件的CD记录。该函数使用一个选择器函数(selector-fn)来匹配需要更新的记录,并通过关键字参数提供更新的内容。我们将逐行解释它的功能和关键点。


    (defun update (selector-fn &key title artist rating (ripped nil ripped-p))
    • 功能: update 函数的目的是更新数据库中的CD记录。
    • 参数:
    • selector-fn: 一个函数,用于选择需要更新的记录。该函数会传递给 funcall,并对每个CD记录进行过滤。
    • &key: 关键字参数,允许用户指定哪些属性需要更新。包括:
      • title: 新的标题(如果提供)。
      • artist: 新的艺术家名称(如果提供)。
      • rating: 新的评分(如果提供)。
      • (ripped nil ripped-p): 是否已翻录。ripped-p 是一个标志,表示是否传递了 ripped 参数。默认值为 nil
    • 要点:
    • &key 用于定义关键字参数,这在Lisp中是一种便捷的方式来传递可选参数。
    • (ripped nil ripped-p) 定义了一个特殊的关键字参数 ripped,并通过 ripped-p 检查该参数是否实际被传递。

      (setf *db*
        (mapcar
         #'(lambda (row)
             (when (funcall selector-fn row)
               (if title (setf (getf row :title) title))
               (if artist (setf (getf row :artist) artist))
               (if rating (setf (getf row :rating) rating))
               (if ripped-p (setf (getf row :ripped) ripped)))
             row) *db*)))
    • 功能: 该部分的核心是使用 mapcar 函数来遍历 *db* 中的每一条CD记录,并对每一条记录进行更新。如果某条记录符合 selector-fn 的选择条件,则根据传入的关键字参数更新相应的字段。
    • 详细解释:
    1. setf *db*:
      • setf 用于修改全局变量 *db*,将其设置为 mapcar 函数的结果。mapcar 的作用是对 *db* 列表中的每一条记录应用指定的函数。
    2. mapcar:
      • mapcar 用于遍历 *db* 列表,并对每条记录 row 应用一个匿名函数(lambda)。
      • 每次迭代都会将当前的 row 传递给匿名函数,匿名函数根据条件选择是否更新该记录。
    3. lambda 函数:
      • 匿名函数(lambda)接收每一条记录 row,并通过 funcall 调用 selector-fn 来决定是否对该记录进行更新。
      • selector-fn 是一个选择器函数,它接收 row 作为参数,返回 tnil,表示是否需要更新该记录。
    4. when (funcall selector-fn row):
      • funcall 调用 selector-fn,并将 row 作为参数传递。如果 selector-fn 对该 row 返回 t,则进入 when 语句块,执行更新操作。
    5. 属性更新操作:
      • if title (setf (getf row :title) title):
      • 如果 title 参数不为 nil,则使用 setf 更新该记录的 :title 属性为新的 title 值。
      • if artist (setf (getf row :artist) artist):
      • 类似地,如果 artist 参数不为 nil,则更新该记录的 :artist 属性。
      • if rating (setf (getf row :rating) rating):
      • 如果 rating 参数不为 nil,则更新该记录的 :rating 属性。
      • if ripped-p (setf (getf row :ripped) ripped):
      • 如果 ripped-pt,表示用户传递了 ripped 参数,则更新该记录的 :ripped 属性。注意这里是通过 ripped-p 来判断是否传递了 ripped 参数,而不是直接判断 ripped 的值。这允许用户显式地将 ripped 设置为 nilt
    6. row 的返回:
      • 无论记录是否被更新,lambda 函数都会返回 row,并将其包含在新的列表中。mapcar 将这些记录组成一个新的数据库列表。

    关键点和难点解析

    • 使用 selector-fn 进行条件选择:
    • 该函数的灵活性体现在它允许用户通过一个选择器函数(selector-fn)来指定需要更新的记录。这个设计非常通用,用户可以传递任意的选择逻辑,从而实现复杂的过滤条件。
    • 关键字参数的使用:
    • 关键字参数(&key)使得函数调用更加灵活。用户可以选择只更新某几个字段,而不必传递所有字段的值。
    • (ripped nil ripped-p) 是一种较为高级的用法,它不仅允许用户传递 ripped 值,还可以检测用户是否传递了这个参数。这使得 ripped 可以显式地设置为 nilt
    • mapcar 的使用:
    • mapcar 是一个常用的函数式编程工具,用于对列表中的每个元素应用一个函数,并返回一个新列表。这里它被用来遍历 *db*,并返回一个更新后的数据库。
    • setfgetf 的使用:
    • setf 是Lisp中的通用赋值操作符,用于修改数据结构中的值。在这里,它被用来更新属性列表(plist)中的值。
    • getf 用于从属性列表中获取指定键的值。通过 setfgetf 的组合,可以修改属性列表的某个键值对。

    示例

    假设数据库 *db* 中有以下CD记录:

    (setq *db* (list (make-cd "Title1" "Artist1" 5 t)
                     (make-cd "Title2" "Artist2" 4 nil)
                     (make-cd "Title3" "Artist3" 3 t)))

    现在,我们希望将所有评分为 5 的CD的标题更新为 "New Title",可以这样调用 update 函数:

    (update (where :rating 5) :title "New Title")

    调用后,数据库中的第一条记录的标题将被更新为 "New Title",而其他记录保持不变。


    结论 🎊

    通过上述步骤,我们用Lisp成功实现了一个简单的数据库,能够存储、查询、更新和删除CD记录。尽管Lisp的语法可能让初学者感到陌生,但它的优雅与强大在实际应用中会让你感受到编程的乐趣。希望你在探索Lisp的旅程中,能够感受到这位优雅舞伴所带来的无限魅力。

    参考文献 📚

    1. 编程之禅. (2021). 用 Lisp 实现一个简单的数据库. Retrieved from 编程之禅
    (defun make-cd (title artist rating ripped)
      (list :title title :artist artist :rating rating :ripped ripped))
    
    (defvar *db* nil)
    
    (defun add-record (cd) (push cd *db*))
    
    (defun dump-db ()
      (dolist (cd *db*)
        (format t "~{~a:~10t~a~%~}~%" cd)))
    
    
    (defun prompt-read (prompt)
      (format *query-io* "~a: " prompt)
      (force-output *query-io*)
      (read-line *query-io*))
    
    (defun prompt-for-cd ()
      (make-cd
       (prompt-read "Title")
       (prompt-read "Artist")
       (OR (parse-integer (prompt-read "Rating") :junk-allowed t) 0)
       (y-or-n-p "Ripped [y/n]: ")))
    
    (defun add-cds ()
      (loop (add-record (prompt-for-cd))
    	(if (not (y-or-n-p "Another?[y/n]: ")) (return))))
    
    (defun save-db (filename)
      (with-open-file (out filename
    		       :direction :output
    		       :if-exists :supersede)
        (with-standard-io-syntax
          (print *db* out))))
    
    
    (defun load-db (filename)
      (with-open-file (in filename)
        (with-standard-io-syntax
          (setf *db* (read in)))))
    
    (defun select (selector-fn)
      (remove-if-not selector-fn *db*))
    
    (defun where (&key title artist rating (ripped nil ripped-p))
      #'(lambda (cd)
          (and
           (if title (equal (getf cd :title) title) t)
           (if artist (equal (getf cd :artist) artist) t)
           (if rating (equal (getf cd :rating) rating) t)
           (if ripped-p (equal (getf cd :ripped) ripped) t))))
    
    (defun update (selector-fn &key title artist rating (ripped nil ripped-p))
      (setf *db*
    	(mapcar
    	 #'(lambda (row)
    	     (when (funcall selector-fn row)
    	       (if title (setf (getf row :title) title))
    	       (if artist (setf (getf row :artist) artist))
    	       (if rating (setf (getf row :rating) rating))
    	       (if ripped-p (setf (getf row :ripped) ripped)))
    	     row) *db*)))
    
    (defun delete-rows (selector-fn)
      (setf *db* (remove-if selector-fn *db*)))

    这段代码实现了一个简单的CD数据库管理系统,可以添加、查询、更新和删除CD记录,并支持数据库的加载和保存。我们将逐步分析每一个部分的功能和关键点。


    1. make-cd: 创建 CD 记录

    (defun make-cd (title artist rating ripped)
      (list :title title :artist artist :rating rating :ripped ripped))
    • 功能: make-cd 函数用于创建一个新的CD记录。每个CD记录是一个列表,包含标题(:title)、艺术家(:artist)、评分(:rating)和是否已翻录(:ripped)。
    • 要点:
    • 使用 list 函数创建一个属性列表(property list,简称 plist),其键(以冒号开头,如 :title)是符号,值是函数参数。
    • 属性列表在Lisp中是一种常见的数据结构,使用 getf 函数可以访问键对应的值。

    2. *db*: 数据库变量

    (defvar *db* nil)
    • 功能: 这是一个全局变量,用于存储所有CD记录。最初它被设置为 nil,表示数据库为空。
    • 要点:
    • *db* 是一个全局变量,遵循Common Lisp中全局变量的命名习惯,即用星号包围变量名。
    • 使用 defvar 定义全局变量,如果该变量已经存在,defvar 不会重新初始化。

    3. add-record: 添加记录到数据库

    (defun add-record (cd) (push cd *db*))
    • 功能: 将一个CD记录添加到数据库中。
    • 要点:
    • 使用 push 将CD记录添加到 *db* 列表的头部。push 是一个高效的操作,因为它直接修改列表的指向。

    4. dump-db: 打印数据库内容

    (defun dump-db ()
      (dolist (cd *db*)
        (format t "~{~a:~10t~a~%~}~%" cd)))
    • 功能: 遍历 *db*,逐条打印每个CD记录的属性和值。
    • 要点:
    • dolist 用于遍历数据库中的每个CD记录。
    • format 函数用于格式化输出。这里的 ~{~a:~10t~a~%~} 格式化字符串用于按键值对的形式打印CD的元数据。

    5. prompt-read: 从用户输入读取数据

    (defun prompt-read (prompt)
      (format *query-io* "~a: " prompt)
      (force-output *query-io*)
      (read-line *query-io*))
    • 功能: 提示用户输入信息,并读取用户输入的字符串。
    • 要点:
    • format 用于显示提示信息。
    • force-output 确保提示信息立即输出,而 read-line 则从用户输入中读取一行文本。

    6. prompt-for-cd: 提示用户输入CD信息

    (defun prompt-for-cd ()
      (make-cd
       (prompt-read "Title")
       (prompt-read "Artist")
       (or (parse-integer (prompt-read "Rating") :junk-allowed t) 0)
       (y-or-n-p "Ripped [y/n]: ")))
    • 功能: 提示用户输入CD的详细信息,并创建并返回一个CD记录。
    • 要点:
    • 使用 prompt-read 函数提示用户输入标题和艺术家。
    • parse-integer 将用户输入的评分转换为整数,如果输入的内容不是有效的数字,则返回 0(使用了 :junk-allowed 选项)。
    • 使用 y-or-n-p 确认CD是否已经翻录。它是一个用于读取 y/n 回答的函数。

    7. add-cds: 循环添加多个CD记录

    (defun add-cds ()
      (loop (add-record (prompt-for-cd))
        (if (not (y-or-n-p "Another?[y/n]: ")) (return))))
    • 功能: 支持用户通过循环方式添加多个CD记录,直到用户选择停止。
    • 要点:
    • loop 用于无限循环,直到用户选择不再添加CD。
    • 每次循环调用 add-record 添加新CD,并通过 y-or-n-p 确认是否继续。

    8. save-db: 保存数据库到文件

    (defun save-db (filename)
      (with-open-file (out filename
                   :direction :output
                   :if-exists :supersede)
        (with-standard-io-syntax
          (print *db* out))))
    • 功能: 将数据库保存到指定文件中。
    • 要点:
    • with-open-file 打开一个文件进行输出,:if-exists :supersede 表示如果文件存在则覆盖。
    • with-standard-io-syntax 设置标准的I/O语法,确保数据结构可以正确地读/写。
    • print 将数据库的内容输出到文件。

    9. load-db: 从文件加载数据库

    (defun load-db (filename)
      (with-open-file (in filename)
        (with-standard-io-syntax
          (setf *db* (read in)))))
    • 功能: 从指定文件中加载数据库。
    • 要点:
    • save-db 类似使用 with-open-file 打开文件进行读取。
    • read 函数从文件中读取数据库数据,并使用 setf 将其赋值给 *db*

    10. select: 选择符合条件的CD记录

    (defun select (selector-fn)
      (remove-if-not selector-fn *db*))
    • 功能: 选择所有满足 selector-fn 条件的CD记录。
    • 要点:
    • remove-if-not 用于过滤列表,保留所有满足 selector-fn 函数返回 true 的元素。

    11. where: 构造选择条件

    (defun where (&key title artist rating (ripped nil ripped-p))
      #'(lambda (cd)
          (and
           (if title (equal (getf cd :title) title) t)
           (if artist (equal (getf cd :artist) artist) t)
           (if rating (equal (getf cd :rating) rating) t)
           (if ripped-p (equal (getf cd :ripped) ripped) t))))
    • 功能: 根据给定的键(如 titleartistrating 等)构造一个选择函数,用于在 select 函数中进行过滤。
    • 要点:
    • &key 允许参数按关键字传递。
    • 返回一个匿名函数(lambda),该函数根据传递的条件对每个CD记录进行匹配。

    12. update: 更新符合条件的CD记录

    (defun update (selector-fn &key title artist rating (ripped nil ripped-p))
      (setf *db*
        (mapcar
         #'(lambda (row)
             (when (funcall selector-fn row)
               (if title (setf (getf row :title) title))
               (if artist (setf (getf row :artist) artist))
               (if rating (setf (getf row :rating) rating))
               (if ripped-p (setf (getf row :ripped) ripped)))
             row) *db*)))
    • 功能: 更新数据库中所有符合 selector-fn 条件的CD记录。
    • 要点:
    • 使用 mapcar 遍历 *db*,对每个符合条件的CD记录进行更新。
    • setf 更新属性列表中的对应属性。

    13. delete-rows: 删除符合条件的CD记录

    • 功能: 删除所有满足 selector-fn 条件的CD记录。
    • 要点:
    • remove-if 移除列表中符合条件的元素。

    (defun delete-rows (selector-fn)
      (setf *db* (remove-if selector-fn *db*)))

    总结

    这段代码实现了一个简单的CD数据库系统,使用Common Lisp的基本数据结构和控制流来管理数据。代码的关键点包括:

    • 使用属性列表存储CD记录。
    • 提供了增删改查的操作。
    • 使用函数式编程风格(如 selector-fn)进行选择和过滤。

    通过这些功能,用户可以灵活地管理和操作CD数据库。

  • 微软推出Windows 11 2024更新:一场科技盛宴的序幕🎉

    在这个快速发展的数字时代,微软再一次给我们带来了惊喜,正式开始推出其Windows 11的2024年更新,即版本24H2。想象一下,微软就像是一位穿着华丽外衣的魔术师,时不时抖出一些令人眼前一亮的新功能,让我们这些普通用户目不暇接。

    🌐 更新的意义

    虽然Windows目前仅占微软2024财年2450亿美元收入的9%,但它的高利润让该产品的增强功能不断推陈出新。Windows 11自2021年推出以来,用户数量以每年50%的速度增长,这似乎证明了“老树开新花”的道理。随着Windows 10的支持将在2025年10月结束,微软显然希望通过持续的更新吸引更多用户。

    ⚡ 功能亮点

    这次的24H2更新,不仅仅是表面光鲜的改进,其背后蕴藏着诸多实用的功能提升。以下是一些值得关注的新特性:

    1. 能源管理的升级:即便你的PC没有电池,你仍然可以通过选择减少功耗的选项来优化能源使用。这就像是在为你的电脑穿上一件“节能外衣”。
    2. 超级助听器功能:如果你有支持蓝牙LE音频的助听器,恭喜你!更新后,你可以直接将其连接到电脑,音频流畅无阻,音量和音质的调整也不再是问题。
    3. 更快速的网络体验:新推出的Wi-Fi 7路由器,能以比Wi-Fi 6更高的速度传输数据。想象一下,下载文件如同火箭般迅速,不再是煎熬的等待。
    4. 简化的Wi-Fi共享:在设置应用中,你可以生成一个二维码,让其他设备轻松连接Wi-Fi。分享网络就像分享美食一样简单。
    5. 丰富的桌面背景:如果你的设备支持HDR信号,可以选择使用.JXR格式的图片作为桌面背景,图像的对比度将会提升,让你的桌面瞬间焕然一新。
    6. 文件压缩的新选择:在文件资源管理器中,右键点击文件组,选择“压缩到…”,你可以在ZIP、7z或TAR格式之间自由切换。不再需要下载额外的程序,简直是懒人福音。

    📥 如何获取新功能

    想要体验这些新特性?只需打开设置应用,前往Windows更新,等着微软推送即可。首批更新将推送给运行22H2和23H2版本的设备,让你在第一时间体验到这些新功能。

    📊 Windows 11 24H2 更新功能概览

    • 能源管理升级
    • 🎧 超级助听器功能
    • 🚀 更快速的网络体验
    • 📶 简化的Wi-Fi共享
    • 🖼️ 丰富的桌面背景
    • 📦 文件压缩新选择

    🔍 结语

    总之,Windows 11的最新更新无疑是微软在操作系统革命中的又一次前进步伐。它不仅为用户带来了更高效的工作体验,还让我们在使用过程中感受到科技的温暖与关怀。期待在未来的日子里,微软能够继续为我们带来更多惊喜,毕竟,科技的旅程才刚刚开始。


    参考文献

    1. Microsoft starts rolling out Windows 11 2024 update, version 24H2. CNBC. (2024, October 1).
人生梦想 - 关注前沿的计算机技术 acejoy.com 🐾 步子哥の博客 🐾 背多分论坛 🐾 知差(chai)网
快取状态: No
内存使用量: 9.26 MB
资料库查询次数: 2
页面产生时间: 0.838 (秒)