2

我的代码中的移动功能有问题。我需要它:

  1. 一种可以移动所有形状的功能,或者,
  2. 具有相同名称的多个函数。

到目前为止,我已经为point、circle 和 polygon提供了具有不同名称的move函数。我不知道如何为图片制作移动功能。

如果你们可以帮助我使用图片移动功能并编辑所有移动功能,以便它们像我开始描述的那样工作。

    ;
    ; POINT
    ;

    (defun make-point ()
      (list (list 0 0) :black))

    (defun x (point)
     (caar point))

    (defun y (point)
      (cadar point))

    (defun set-x (point new-x)
      (setf (caar point) new-x)
      point)

    (defun set-y (point new-y)
      (setf (cadar point) new-y)
      point)

    (defun move (point dx dy)
     (set-x point (+ (x point) dx))
     (set-y point (+ (y point) dy))
     point)

    ;
    ; CIRCLE
    ;

    (defun make-circle ()
      (list  (make-point) 1 :black))

    (defun center (circle)
      (car circle))

    (defun radius (circle)
      (cadr circle))

    (defun set-radius (circle new-rad)
      (if (> 0 new-rad)
          (format t "Polomer ma byt kladne cislo, zadali ste : ~s" new-rad)
        (setf (cadr circle) new-rad))
      circle)

    (defun movec (circle dx dy)
      (move (center circle) dx dy)
     circle)

    ;
    ; POLYGON
    ;

    (defun make-polygon ()
      (list nil :black))

    (defun items (shape)
     (car shape))

    (defun set-items (shape val)
      (setf (car shape) val)
      shape)

    (defun movep (polygon dx dy)
      (mapcar (lambda (b) (move b dx dy))  (items polygon))
      polygon)

    ;
    ; PICTURE
    ;

    (defun make-picture ()
      (list nil :black))

    ;(defun movepi (picture dx dy)) 

    ; items, set-items used for polygon and picture
4

2 回答 2

6

您的对象只是列表,您将很难区分不同类型的形状。您可以在列表前添加关键字、标签类型(例如:point:circle等),以根据该标签更好地调度您的移动操作,但这将是重新发明轮子,也就是对象。

简单的函数和列表

一种可以移动所有形状的功能

您可以这样做,前提是您可以分派您正在使用的实际对象类型。move应该能够知道被移动的是什么样的形状。如果可以将对象类型添加为列表的 CAR,请更改数据结构,并使用 CASE 调度,然后根据需要移动每个对象。

或具有相同名称的多个函数。

这是不可能的,至少在同一个包中是不可能的。

克洛斯

(defpackage :pic (:use :cl))
(in-package :pic)

多个形状都有颜色,所以让我们定义一个类来表示具有颜色组件的对象:

(defclass has-color ()
  ((color :initarg :color :accessor color)))

如果你不熟悉CLOS(Common Lisp Object System),上面定义了一个名为 的类has-color,没有超类和单个插槽,color. 访问器命名读取器写入器通用函数,以便您可以(color object)检索对象并将(setf (color object) color)对象的颜色设置为颜色。:initarg用于定义要在 中使用的关键字参数make-instance

下面,我们定义 a point,它具有颜色和附加值xy坐标。

(defclass point (has-color)
  ((x :initarg :x :accessor x)
   (y :initarg :y :accessor y)))

圆也是一样的:

(defclass circle (has-color)
  ((center :initarg :center :accessor center)
   (radius :initarg :radius :accessor radius)))

还有一个多边形:

(defclass polygon (has-color)
  ((points :initarg :points :accessor points)))

最后,图片是一系列形状:

(defclass picture ()
  ((shapes :initarg :shapes :accessor shapes)))

您可以按如下方式制作一个圆圈:

(make-instance 'circle
               :center (make-instance 'point :x 10 :y 30)
               :color :black))

如果需要,您还可以定义更短的构造函数。

现在,您可以对move对象使用通用函数。您首先使用 定义它DEFGENERIC,它声明了泛型函数的签名以及其他选项。

(defgeneric move (object dx dy)
  (:documentation "Move OBJECT by DX and DY"))

现在,您可以向该泛型函数添加方法,并且您的泛型函数将基于一个或多个专业化器和/或限定符分派给它们。

例如,您按如下方式移动一个点:

(defmethod move ((point point) dx dy)
  (incf (x point) dx)
  (incf (y point) dy))

可以看到我们move是根据第一个参数的类进行专门化的,这里命名为point。当绑定到的值point是 class时应用该方法pointINCF隐式调用(setf x)and的调用(setf y),如上定义。

移动一个圆意味着移动它的中心:

(defmethod move ((circle circle) dx dy)
  (move (center circle) dx dy))

您可以在任何类上专门化一个泛型函数,例如标准SEQUENCE类。它以相同的偏移量移动序列中的所有对象:

(defmethod move ((sequence sequence) dx dy)
  (map () (lambda (object) (move object dx dy)) sequence))

这对多边形很有用:

(defmethod move ((polygon polygon) dx dy)
  (move (points polygon) dx dy))

还有图片:

(defmethod move ((picture picture) dx dy)
  (move (shapes picture) dx dy))

不可变版本

您也可以move构建新实例,但这需要以某种方式复制现有对象。一种简单的方法是使用一个通用函数,该函数用源实例填充目标实例:

(defgeneric fill-copy (source target)
  (:method-combination progn))

这里的方法组合是指所有满足的方法fill-copy都运行,而不是只运行最具体的一个。这progn表明所有方法都在一个progn块中运行,一个接一个。通过上面的定义,我们可以定义一个简单的copy-object泛型函数:

(defgeneric copy-object (source)
  (:method (source)
    (let ((copy (allocate-instance (class-of source))))
      (fill-copy source copy)
      copy)))

上面定义了一个名为 的通用函数copy-object,以及 T 类型对象(任何对象)的默认方法。 ALLOCATE-INSTANCE创建一个实例但不初始化它。该方法用于FILL-COPY复制槽值。

例如,您可以定义如何复制color具有颜色的任何对象的插槽:

(defmethod fill-copy progn ((source has-color) (target has-color))
  (setf (color target) (color source)))

请注意,您在这里有多个分派:源对象和目标对象都必须属于has-color要调用的方法的类。progn方法组合允许fill-copy在不同的、解耦的方法之间分配工作:

(defmethod fill-copy progn ((source point) (target point))
  (setf (x target) (x source))
  (setf (y target) (y source)))

如果您指出fill-copy,则可以根据 的类层次结构应用两种方法point:一种是为 定义的has-color,另一种是专门针对point该类的(用于两个参数)。progn方法组合确保两者都被执行。

由于某些插槽可以未绑定,因此可能会fill-copy失败。我们可以通过添加一个错误处理程序来解决 fill-copy这个问题:

(defmethod fill-copy :around (source target)
  (ignore-errors (call-next-method)))

(call-next-method)表单调用其他方法(由progn限定符定义的方法),但我们将其包装在ignore-errors. 这里没有定义颜色,但复制成功:

(copy-object (make-point :x 30 :y 20))
=> #<POINT {1008480D93}>

我们现在可以保留我们现有的、变异的move方法,并将它们包装在一个:around专门的方法中,首先制作一个副本:

(defmethod move :around (object dx dy)
  ;; copy and mutate
  (let ((copy (copy-object object)))
    (prog1 copy
      (call-next-method copy dx dy))))

为了看看会发生什么,定义一个方法PRINT-OBJECT

(defmethod print-object ((point point) stream)
  (print-unreadable-object (point stream :identity t :type t)
    (format stream "x:~a y:~a" (x point) (y point))))

现在,移动一个点会创建一个新点:

(let ((point (make-instance 'point :x 10 :y 20)))
  (list point (move point 10 20)))

=> (#<POINT x:10 y:20 {1003F7A4F3}> #<POINT x:20 y:40 {1003F7A573}>)

您仍然需要更改 SEQUENCE 类型的方法,该类型当前丢弃 的返回值move,但除此之外,对现有代码几乎没有更改。

另请注意,上述方法主要用作描述 CLOS 各种用途的一种方式,实际上您可能会选择一种或另一种方式来移动点(可变或不可变),或者您将拥有不同的功能而不是单一的通用的一个(例如 mut-move 和 move)。

于 2018-10-17T15:46:36.393 回答
2

粗略的草图,标签形状:

(defun p (x y) (list x y))
(defun make-shape (type points colour data)
  (list* type points colour data))
(defmacro defshape (name args &key verify-points verify-args)
  "define the function (make-NAME points ARGS...)
to make a shape of type :NAME. Optionally 
evaluate the form VERIFY-ARGS with the
lambda-list ARGS bound and call the
function VERIFY-POINTS with the points of 
the shape, ignoring its result."
  (let ((type (intern name (symbol-package :key)))
        (fun (intern (concatenate 'String "MAKE-" name) (symbol-package name)))
        (all (gensym "ARGS"))
        (colour (gensym "COLOUR"))
        (points (gensym "POINTS")))
    `(defun ,fun (,points ,colour &rest ,all)
       (destructuring-bind ,args ,all
         ,verify-args
         ,(if verify-points `(funcall ,verify-points ,points))
         (make-shape ,type ,points ,colour ,all))))

(defun singlep (list) (and list (null (cdr list))))
(defshape point () :verify-points #'singlep
(defshape circle (radius) :verify-args (assert (realp radius) radius)
          :verify-points #'singlep)
(defshape polygon ())

你可以使用这个:

CL-USER> (make-circle (list (p 0 0)) :black 2)
(:CIRCLE ((0 0)) :BLACK)
CL-USER> (make-point (list (p 1 2)) :blue)
(:POINT ((1 2)) :BLUE)
CL-USER> (make-polygon (list (p 0 0) (p 0 1) (p 1 0)) :red)
(:POLYGON ((0 0) (0 1) (1 0)) :RED)

你可以写一些函数:

(defun map-points (function shape)
  (destructuring-bind (type points colour &rest data)
        shape
    (make-shape type (mapcar function points) colour data)))

并应用它们:

CL-USER> (map-points (lambda (p) (list (1+ (first p)) (second p))) '(:POLYGON ((0 0) (0 1) (1 0)) :RED))
(:POLYGON ((1 0) (1 1) (2 0)) :RED)

并解决您的问题:

(defun move (dx dy shape)
  (map-points (lambda (p) (destructuring-bind (x y) p (list (+ x dx) (+ y dy)))) shape))

您可能想要的另一件事是基于形状的类型(即CAR)的大案例,您根据将类型映射到哈希表中的某些内容或将某些内容放入其符号 plist 中进行调度。

于 2018-10-17T18:49:37.747 回答