Common lisp :KEY parameter use

前端 未结 3 529
心在旅途
心在旅途 2021-01-03 03:58

The :KEY parameter is included in some functions that ship with Common Lisp. All of the descriptions that I have found of them are unhelpful, and :KEY

3条回答
  •  有刺的猬
    2021-01-03 04:18

    Imagine that we have a list of cities:

    (defparameter *cities*
       ; City       Population  Area km^2
      '((Paris      2265886     105.4)
        (Mislata    43756       2.06)
        (Macau      643100      30.3)
        (Kallithea  100050      4.75)
        (Nea-Smyrni 73090       3.52)
        (Howrah     1072161     51.74)))
    

    Now we can compute the population density in people/km^2

    (defun city-density (city)
      "the density is the population number divided by the area"
      (/ (second city) (third city)))
    

    Now we want to compute a list of all cities which have a density less than 21000 people/km^2.

    We remove all larger ones from the list and are providing a :test-not function. We need to provide an anonymous function which does the test and computes the density of the city to compare.

    CL-USER 85 > (remove 21000 *cities*
                         :test-not (lambda (a b)
                                     (>= a (city-density b))))
    
    ((NEA-SMYRNI 73090 3.52) (HOWRAH 1072161 51.74))
    

    We can write it simpler without the anonymous function by providing the numeric :test-not function >= and use the city-density function as the key to compute the value from each provided cities:

    CL-USER 86 > (remove 21000 *cities* :test-not #'>= :key #'city-density)
    
    ((NEA-SMYRNI 73090 3.52) (HOWRAH 1072161 51.74))
    

    So having both a test predicate and a key function makes it easier to provide the building blocks for sequence computations...

    Now imagine that we use CLOS and a list of city CLOS objects:

    (defclass city ()
      ((name :initarg :name :reader city-name)
       (population :initarg :population :reader city-population)
       (area :initarg :area :reader city-area)))
    
    (defparameter *city-objects*
      (loop for (name population area) in *cities*
            collect (make-instance 'city
                                   :name name
                                   :population population
                                   :area area)))
    
    (defmethod density ((c city))
      (with-slots (population area)
          c
        (/ population area)))
    

    Now we compute the list as above:

    CL-USER 100 > (remove 21000 *city-objects* :test-not #'>= :key #'density)
    (# #)
    
    CL-USER 101 > (mapcar #'city-name *)
    (NEA-SMYRNI HOWRAH)
    

    If we have the density as a slot with a getter, we can do this:

    (defclass city ()
      ((name :initarg :name :reader city-name)
       (population :initarg :population :reader city-population)
       (area :initarg :area :reader city-area)
       (density :reader city-density)))
    
    (defmethod initialize-instance :after ((c city) &key)
      (with-slots (density)
          c
        (setf density (density c))))
    
    (defparameter *city-objects*
      (loop for (name population area) in *cities*
            collect (make-instance 'city
                                   :name name
                                   :population population
                                   :area area)))
    

    Now we compute the list as above, but the key is the getter of the density slot:

    CL-USER 102 > (remove 21000 *city-objects* :test-not #'>= :key #'city-density)
    (# #)
    
    CL-USER 103 > (mapcar #'city-name *)
    (NEA-SMYRNI HOWRAH)
    

提交回复
热议问题