所以,首先让我们将这些列表称为((key value) ...) mlists(如果您愿意,可以称为“奖牌列表”):它们实际上是关联列表(alists),但关联列表通常采用((key . value) ...) 的形式,所以我想要另一个名字。
让我们编写一个通用函数update-mlist 来更新一个mlist。它将:
- 如果无事可做就停下来;
- 否则,如果 mlist 的第一个元素是它要查找的元素,则根据该元素的值调用其更新程序函数并返回一个新的 mlist;
- 否则返回包含现有第一个元素的新 mlist,并更新 mlist 的其余部分。
这里是:
(defun update-mlist (mlist key updater)
;; update an mlist, replacing the element with key KEY by calling
;; UPDATER on its value. An mlist is of the form ((key value) ...).
(cond
((null mlist)
;; no more to process: we're done
'())
((eql (first (first mlist)) key)
;; found it: call the updater on the value and return the new
;; mlist
(cons (list (first (first mlist))
(funcall updater (second (first mlist))))
(rest mlist)))
(t
;; didn't find it: search the rest
(cons (first mlist)
(update-mlist (rest mlist) key updater)))))
我们可以试试这个:
> (update-mlist '((able 1) (baker 2) (charlie 2))
'charlie
(lambda (v)
(+ v 1)))
((able 1) (baker 2) (charlie 3))
好的。
所以,现在,让我们将奖牌列表存储在一个变量中,以便我们讨论它:
(defvar *medals* '((bolt ((gold 4)
(silver 2)))
(farah ((gold 3)
(silver 1)
(bronze 1)))
(ottey ((bronze 3)))))
*medals* 的有趣之处在于它是一个 mlist,其中每个元素的值都是一个 mlist。所以我们要做的事情是使用update-mlist 更新函数本身调用update-mlist 来更新奖牌列表。好的,好吧,我们可以这样写:
(defun update-medals (medals person medal updater)
;; update the medal mlist for PERSON, calling UPDATER on the value
;; of the MEDAL medal
(update-mlist medals person
(lambda (medal-mlist)
(update-mlist medal-mlist
medal
updater))))
就是这样。假设farah 刚刚获得金牌:我们想将他们的gold 计数增加 1:
> (update-medals *medals* 'farah 'gold
(lambda (count)
(+ count 1)))
((bolt ((gold 4) (silver 2)))
(farah ((gold 4) (silver 1) (bronze 1)))
(ottey ((bronze 3))))
但是我们有一个小问题:
> (update-medals *medals* 'ottey 'gold
(lambda (count)
(+ count 1)))
((bolt ((gold 4) (silver 2)))
(farah ((gold 3) (silver 1) (bronze 1)))
(ottey ((bronze 3))))
亲爱的。
所以,好吧,我们可以解决这个问题:让我们更改 update-mlist,这样,如果它到达 mlist 的末尾,它会提供一个回退:
(defun update-mlist (mlist key updater fallback)
;; update an mlist, replacing the element with key KEY by calling
;; UPDATER on its value. An mlist is of the form ((key value) ...).
;; If we reach the end of the list add an entry for KEY with FALLBACK
(cond
((null mlist)
;; no more to process: add the fallback
(list (list key fallback)))
((eql (first (first mlist)) key)
;; found it: call the updater on the value and return the new
;; mlist
(cons (list (first (first mlist))
(funcall updater (second (first mlist))))
(rest mlist)))
(t
;; didn't find it: search the rest
(cons (first mlist)
(update-mlist (rest mlist) key updater fallback)))))
我们可以测试一下:
> (update-mlist '((able 1) (baker 2) (charlie 3))
'zebra
(lambda (v)
(+ v 1))
26)
((able 1) (baker 2) (charlie 3) (zebra 26))
我们需要相应地更改update-medals:
(defun update-medals (medals person medal updater fallback)
;; update the medal mlist for PERSON, calling UPDATER on the value
;; of the MEDAL medal. If there is no entry add a fallback. If
;; there is no entry for the person add a fallback as well
(update-mlist medals person
(lambda (medal-mlist)
(update-mlist medal-mlist
medal
updater
fallback))
(list medal fallback)))
这很有效:
> (update-medals *medals* 'ottey 'gold
(lambda (count)
(+ count 1))
1)
((bolt ((gold 4) (silver 2)))
(farah ((gold 3) (silver 1) (bronze 1)))
(ottey ((bronze 3) (gold 1))))
> (update-medals *medals* 'hercules 'gold
(lambda (count)
(+ count 100))
100)
((bolt ((gold 4) (silver 2)))
(farah ((gold 3) (silver 1) (bronze 1)))
(ottey ((bronze 3)))
(hercules (gold 100)))
好的,最后我们可以将这一切包装在一个 award-medal 函数中:
(defun award-medal (medals person medal &optional (number 1))
(update-medals medals person medal
(lambda (c)
(+ c number))
number))
现在
> (award-medal *medals* 'bolt 'gold)
((bolt ((gold 5) (silver 2)))
(farah ((gold 3) (silver 1) (bronze 1)))
(ottey ((bronze 3))))
> (award-medal *medals* 'ottey 'gold)
((bolt ((gold 4) (silver 2)))
(farah ((gold 3) (silver 1) (bronze 1)))
(ottey ((bronze 3) (gold 1))))
> (award-medal *medals* 'hercules 'diamond 10000)
((bolt ((gold 4) (silver 2)))
(farah ((gold 3) (silver 1) (bronze 1)))
(ottey ((bronze 3)))
(hercules (diamond 10000)))
您可能已经注意到,每次我调用这些函数中的一个时,就好像这是第一次:那是因为它们是函数,它们有参数和返回值,而值他们返回的是新结构:他们不会破坏性地修改他们的论点。这意味着它们都更容易推理和理解,因为它们就是所谓的referentially transparent,并且可以轻松安全地组合它们:
> (award-medal (award-medal *medals* 'bolt 'gold)
'ottey 'silver)
((bolt ((gold 5) (silver 2)))
(farah ((gold 3) (silver 1) (bronze 1)))
(ottey ((bronze 3) (silver 1))))
好吧,我们也可以编写一个小函数来实现这一点:
(defun award-medals (medals award-mlist)
(if (null award-mlist)
medals
(award-medals (award-medal medals
(first (first award-mlist))
(second (first award-mlist)))
(rest award-mlist))))
现在
> (award-medals *medals*
'((bolt gold) (ottey silver) (farah bronze)))
((bolt ((gold 5) (silver 2)))
(farah ((gold 3) (silver 1) (bronze 2)))
(ottey ((bronze 3) (silver 1))))
最后两件事:
-
update-mlist(两个版本)有什么“错误”。如果您的 mlist 中有很多人,会发生什么?
- 你能写一个
award-medals 的版本,它并不真正关心整个奖牌授予的事情,而且它可以为任何功能做这个技巧吗?这会有用吗?