1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006
|
; Demonstration of classes in XLISP by implementing various Smalltalk
; Collection classes.
; Author: Tom Almy
; Date: September 1996
; NOTE -- you should probably check out EXAMPLE.LSP, TURTLES.LSP, and
; BLOCKS.LSP first as they are somewhat simpler.
#-:classes (load "classes") ; We'll use these nice macros
; We will put everyting in a package to keep it out of the user name space
#+:packages (unless (find-package "CLASSES")
(make-package "CLASSES" :use '("XLISP")))
(in-package "CLASSES")
; List the symbols available on the outside -- in this case the class names.
; The message selectors are all in the keyword package so they don't need
; to be exported.
(export '(Collection Set Bag Dictionary SequenceableCollection
Array OrderedCollection SortedCollection Interval))
; Our basic Collection class is "abstract" -- it's just defined to
; subclass into useful types of collections. We'll define a single instance
; variable: "data" contains the collection's data, the format to be
; defined by the subclass. Various subclasses will define any additional
; instance variables.
; The actual collections used in applications will be created from subclasses
; of Collection. This demo will implement:
; Bag -- an unordered collection of objects
; Set -- like a bag, but no duplicate elements
; Dictionary -- access elements using symbolic keys
; SequenceableCollection -- Abstract class which is subclassed into:
; Array -- elements have a sequence. Collection has a fixed size
; OrderedCollection -- same as Array but no fixed size, can add/delete from
; either end.
; SortedCollection -- An Ordered collection with a colating sequence
; Interval -- contains a constant sequence
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; THE COLLECTION CLASS ;
; ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defclass Collection (data))
; The defclass macro defines a new class, which is bound to the symbol
; "Collection". The macro also defines several default methods for instance
; variable access (:data in this case), and instance initialization (:isnew).
; Unlike Smalltalk, XLISP has no class methods. In Smalltalk you create an
; instance of a class by sending a message to the class. In XLISP, the classes
; are members of class Class, and you create an instance by sending the class
; a message with the selector :new. i.e. (send MyClass :new <xxx>) where xxx
; are 0 or more arbitrary expressions. This executes the :new method in class
; Class which will create an object which is a member of the desired class
; (call it newobj) and then does a (send newobj :isnew <xxx>). The :isnew
; method gets the expressions, which it can then use to customize the
; initiation. So, basically, the :isnew method in a class takes the place of
; the class methods of Smalltalk. Not as functional, but it usually does all
; that is needed. The class Array demonstrates how two instance creation
; methods can be supported via the use of keyword arguments.
;;;;;;;;;
; The following group of methods are "private" methods in that they are not
; intended for use in applications, but just to aid in implementation.
; :notImplemented provides a nice error message for messages that aren't
; handled by our class. It's not really necessary to define these!
(defmethod Collection :notImplemented (msg)
(error "~s not handled in class ~a"
msg
(send (send self :class) :pname)))
; Here's the difference:
; >(send x :foo) :notImplemented USED
; error: no method for this message - :foo
; >(send x :foo) :notImplemented NOT USED
; error: :foo not handled in class Bag
; :map is a mapcar like mapping function for the collection.
; This version only works when data instance variable is sequence of
; collection elements. We will have to override the method for subclasses
; that maintain their data differently.
(defmethod Collection :map (fcn) (map 'cons fcn data))
; :addAll will add the elements in the argument collection to this
; collection. We'll extend this definition so it works with sequences as
; well. It won't work with Arrays (which are a fixed size), Intervals
; (which are not alterable), or Dictionaries (which require keys).
(defmethod Collection :addAll (arg)
(if (or (listp arg) (arrayp arg) (stringp arg))
; Use map when argument is a sequence
(map nil (lambda (x) (send self :add x)) arg)
; Otherwise, send :map to the argument collection
(send arg :map (lambda (x) (send self :add x))))
self)
; Override default "isnew" to disallow creating abstract collections.
; There is no reason for any program to create an instance of Collection.
(defmethod Collection :isnew (&rest dummy)
(error "Don't create collections of class \"Collection\""))
;;;;;;;;;
; Now we will define some "public" methods for Collection. Most will be
; overriden in a subclass. The rest we will provide with a common default
; functionality.
; :prin1 determines how an object is printed. The default is to print
; the objects class and unique ID. We want to do better than that if the
; collection is small enough to easily display, say 5 or fewer elements
(defmethod Collection :prin1 (&optional (stream *standard-output*))
(let ((contents (send self :asList)) ; get collection as a list
(cls(send(send self :class):pname))) ; and get our class' name
(cond ((null contents)
(format stream "#<An empty ~a>" cls))
((< (length contents) 6)
(format stream "#<~a:~{ ~s~}>" cls contents))
(t
(format stream "#<~a:~5{ ~s~} ...>" cls contents)))))
; :storeon is used to create an expression which, when executed, will create a
; copy of the object. The Default method, part of class Object, won't work
; for classes that override :isnew, and all Collection classes do.
(defmethod Collection :storeon ()
(list 'send
(list 'send
(intern (send (send self :class) :pname))
:new)
:addAll
(list
'quote
(send self :asList))))
; :at will fetch an element from an "sequenceable collection"
; Not all collections have the concept of sequencing.
(defmethod Collection :at (arg) (send self :notImplemented :at))
; :atput will store an element into a "sequenceable collection".
(defmethod Collection :atPut (arg1 arg2) (send self :notImplemented :atPut))
; :first will fetch the first element of the collection, where appropriate.
; :last does the same thing but for the last element.
(defmethod Collection :first () (send self :notImplemented :first))
(defmethod Collection :last () (send self :notImplemented :last))
; :add will store (one or more copies of) an element into a collection
; :addFirst will add to the start of a collection. These two are not
; implemented for all classes.
(defmethod Collection :add (arg &optional value)
(send self :notImplemented :add))
(defmethod Collection :addFirst (arg)
(send self :notImplemented :addFirst))
(defmethod Collection :addLast (arg)
(send self :notImplemented :addLast))
; Delete the specified, first, or last element
(defmethod Collection :remove (arg)
(send self :notImplemented :remove))
(defmethod Collection :removeFirst ()
(send self :notImplemented :removeFirst))
(defmethod Collection :removeLast ()
(send self :notImplemented :removeLast))
; :size -- Get the size of the the Collection. This will work for
; most subclasses.
(defmethod Collection :size () (length data))
; :empty -- Returns T if collection has no elements
(defmethod Collection :empty () (zerop (send self :size)))
; :includes tells us if a object is a member of the collection
; This version only works when data instance variable is sequence of
; collection elements
(defmethod Collection :includes (arg)
(if (position arg data) t nil))
; :species returns the class similar to the current class to create new
; objects
(defmethod Collection :species ()
(send self :class))
; :do is like :map but returns nothing
; :collect is like :map, but returns a new collection.
; :select returns a collection of elements for which the predicate function
; returns non-NIL.
; These are generic enough to work for any of the Collection subclasses
; except Array, which requires an argument to :new,
; however in many cases they could be overridden for speed.
; Smalltalk defines these and a number of similar functions.
(defmethod Collection :do (fcn) (send self :map fcn) nil)
(defmethod Collection :collect (fcn)
(send (send (send self :species) :new) :addAll (send self :map fcn)))
(defmethod Collection :select (fcn)
(let ((result
(mapcan (lambda (x)
(when (funcall fcn x)
(list x)))
(send self :asList))))
(send (send (send self :species) :new) :addAll result)))
; Our final assortment of Collection methods create copies of the object in
; one of several Collection subclasses or as an LISP list.
; :asList will return the collection as a LISP linked list.
(defmethod Collection :asList () (send self :map #'identity))
; :asBag will return the collection as a Bag
(defmethod Collection :asBag ()
(let ((result (send Bag :new)))
(send result :addAll self)
result))
; :asSet will return the collection as a Set
(defmethod Collection :asSet ()
(let ((result (send Set :new)))
(send result :addAll self)
result))
; :asArray will return the collection as an Array
(defmethod Collection :asArray ()
(send Array :new :initial (send self :asList)))
; :asOrderedCollection will return the collection as an OrderedCollection
(defmethod Collection :asOrderedCollection ()
(let ((result (send OrderedCollection :new)))
(send result :addAll self)
result))
; :asSortedCollection will return the collection as an OrderedCollection
(defmethod Collection :asSortedCollection (&optional (fcn '<))
(let ((result (send SortedCollection :new fcn)))
(send result :addAll self)
result))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; THE SET CLASS ;
; ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Our first collection will be "Set". Initialization doesn't have to do
; anything since instance variables are initialized to NIL.
; We will use "eql" as the equality test
;
(defclass Set () nil Collection)
(defmethod Set :isnew ())
; We will need :add. But we will ignore the count.
(defmethod Set :add (arg &optional (count 1))
(setq data (adjoin arg data))
; Methods typically return (or "answer" in Smalltalk)
; the object, which is bound to "self", if there is
; nothing more appropriate.
self)
; We also need to define :remove
(defmethod Set :remove (arg)
(let ((pos (position arg data))) ; Find (first) instance
(when pos ; Delete found element
(if (zerop pos)
(setq data (cdr data))
(setf (cdr (nthcdr (1- pos) data))
(nthcdr (1+ pos) data))))
self))
; All the other methods inherited from Collection will work fine
; At last we can test out some collections!
; > (setq x (send Set :new)) Create a new set
; #<An empty Set>
; Note that if your system says "#<An empty SET>" that means you have
; *readtable-case* set to :upcase. It's nothing to be concerned about, but
; if you want the output to match, start over with *readtable-case* set to
; :invert.
; > (send x :add 3) Add the element "3"
; #<Set: 3>
; > (send x :add 1) Add the element "1"
; #<Set: 1 3>
; > (send x :add 3) Add another 3 -- it's ignored!
; #<Set: 1 3>
; > (send x :addAll '(1 2 3 4 5)) Add five elements
; #<Set: 5 4 2 1 3>
; We see the order has changed! This doesn't matter because these collections
; are defined to have no order.
; > (send x :remove '3) Remove element "3"
; #<Set: 5 4 2 1>
; > (send x :select #'evenp) Create a set with even elements of x
; #<Set: 2 4>
; > (send x :collect #'1+) Create a set with incremented
; elements of x
; #<Set: 2 3 5 6>
; > (let ((cnt 0)) (send x :do (lambda (x) (incf cnt x))) cnt)
; 12 Summing all the elements in the set
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; THE BAG CLASS ;
; ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Our next Collection class will be "Bag" which is an unordered collection
; of objects that we will implement with a hash table. The table value will
; be the number of occurances of the object in the collection.
; It's difficult to calculate the number of elements in a Bag, so we will
; maintain a running total in a new instance variable, size. The defclass
; function will create a :size method for us!
;
; After we've defined this class, we can finally start testing things out.
(defclass Bag (size) nil Collection)
; Because the data in a Bag will be a hash table instead of a list, we
; need to have "isnew" allocate a hash table.
; The entry equality test will be "eql"
(defmethod Bag :isnew nil
(setf (send self :data) (make-hash-table)
(send self :size) 0))
; We could have done this with "(setf data (make-hash-table) size 0)"
; but this technique is more rigorous.
; The method :add will insert one or more copies of an object in the collection
; We need to adjust the size instance variable when we add objects
(defmethod Bag :add (arg &optional (count 1))
(setf (gethash arg data) (+ (gethash arg data 0) count)
size (+ size count))
self ; Most methods return Self if there isn't anything else
) ; that is reasonable
; The method :remove will delete an object from the collection
; We need to adjust the size instance variable when we delete objects
(defmethod Bag :remove (arg)
(let ((cnt (gethash arg data)))
(when cnt ; element found
(setq size (1- size))
(if (= cnt 1)
(remhash arg data) ; delete if count would be 0
(setf (gethash arg data) (1- cnt))))
self
))
; We have to override the definition of :includes since data is stored
; differently in a bag than as a linked list.
(defmethod Bag :includes (arg)
(if (gethash arg data) t nil))
; We have to override the definition of :map since data is stored
; differently in a bag than as a linked list.
; Even though :collect is similar, we don't need to redefine it since
; Collection :collect uses :map to do its work.
(defmethod Bag :map (fcn)
(if data ; If in the rare case data isn't set up, we abort
(let (result)
(maphash (lambda (arg count)
(dotimes (i count)
(push (funcall fcn arg) result)))
data)
(nreverse result))
nil))
; Now for some Bag examples:
; > (setq y (send Bag :new)) Create a new bag, y
; #<An empty Bag>
; > (send y :add 3) As with set, add 3, 1, 3
; #<Bag: 3>
; > (send y :add 1)
; #<Bag: 3 1>
; > (send y :add 3)
; #<Bag: 3 3 1> Now there can be multiple copies!
; > (send y :addAll x) Add all the elements of Set x
; #<Bag: 5 4 3 3 2 ...> Elipsis means too many to display
; > (send y :asList) Use :asList to see entire contents
; (5 4 3 3 2 1 1)
; > (send y :remove 4)
; #<Bag: 5 3 3 2 1 ...> Remove still works
; > (send y :select #'oddp) Try :select :collect and :do
; #<Bag: 5 3 3 1 1>
; > (send (send y :collect #'1+) :asList)
; (6 4 4 3 2 2)
; > (let ((cnt 0)) (send y :do (lambda (x) (incf cnt x))) cnt)
; 15
; > (send y :asSet) Converting a Bag to a Set
; #<Set: 1 2 3 5> will delete duplicates
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; THE DICTIONARY CLASS ;
; ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The Dictionary class will be implemented using a hash table like a Bag.
; The hash table will use #'equal for comparisons, allowing string keys
;
(defclass Dictionary () nil Collection)
(defmethod Dictionary :isnew nil
(setf (send self :data) (make-hash-table :test #'equal)))
; Getting the size of a Dictionary is slightly different than the default
(defmethod Dictionary :size () (hash-table-count data))
; We need to define the :at and :atPut methods. :at will be extended
; to allow a keyword argument "ifAbsent" to supply the return value.
; It's a closure, just like in Smalltalk
(setq gened (gensym)) ; We need a unique symbol
(defmethod Dictionary :at (key &key ifAbsent)
(let ((value (gethash key data '#.gened)))
(if (eq value '#.gened)
(if ifAbsent
(funcall ifAbsent)
nil)
value)))
(defmethod Dictionary :atPut (key value)
(setf (gethash key data) value)
self)
; :addAll needs to be redefined, and requires a list of key-value pairs.
; This method makes :storeon much simpler.
(defmethod Dictionary :addAll (arg)
(if (or (listp arg) (arrayp arg) (stringp arg))
; Use map when argument is a sequence
(map nil
(lambda (x) (send self :atPut (first x) (second x)))
arg)
; Otherwise, send :map to the argument collection
(send arg
:map
(lambda (x)
(send self :atPut (first x) (second x)))))
self)
; :remove won't work for a Dictionary, since we want to remove key/value
; associations. Thus we have :removeKey, with an optional ifAbsent.
(defmethod Dictionary :removeKey (key &key ifAbsent)
(if (eq (gethash key data '#.gened) '#.gened)
(progn
(remhash key data)
(setq count (1- count)))
(when ifAbsent (funcall ifabsent)))
self)
(unintern gened) ; We don't need this symbol anymore
; :keys returns a set of the keys
(defmethod Dictionary :keys ()
(let (list)
(maphash (lambda (key value) (setq list (cons key list)))
data)
(send (send Set :new) :addAll list)))
; :values returns a bag of the values
(defmethod Dictionary :values ()
(let (list)
(maphash (lambda (key value) (setq list (cons value list)))
data)
(send (send Bag :new) :addAll list)))
; :map is defined to work over the values
(defmethod Dictionary :map (fcn)
(let (list)
(maphash (lambda (key value)
(setq list (cons (funcall fcn value) list)))
data)
list))
; We have to override the definition of :includes since data is stored
; differently in a Dictionary than as a linked list.
(defmethod Dictionary :includes (arg)
(if (position arg (send self :asList)) t nil))
; :collect, :select aren't appropriate
(defmethod Dictionary :collect (arg)
(send self :notImplemented :collect))
(defmethod Dictionary :select (arg)
(send self :notImplemented :select))
; :prin1 needs to be overridden to show both keys and data
(defmethod Dictionary :prin1 (&optional (stream *standard-output*))
(let (contents ; get collection as a list
; and get our class' name
; (it might not be "Dictionary")
(cls (send (send self :class) :pname)))
(maphash (lambda (x y)
(setq contents (cons (list x y) contents)))
data)
(cond ((null contents)
(format stream
"#<An empty ~a>" cls))
((< (length contents) 6)
(format stream
"#<~a:~{ ~s~}>" cls contents))
(t
(format stream
"#<~a:~5{ ~s~} ...>" cls contents)))))
; A different :storeon is needed as well
(defmethod Dictionary :storeon ()
(let (contents) ; get collection as a list
(maphash (lambda (x y)
(setq contents (cons (list x y) contents)))
data)
(list 'send
(list 'send 'Dictionary :new)
:addAll
(list
'quote
contents))))
; Class Dictionary examples
; > (setq z (send Dictionary :new)) Create a new dictionary
; #<An empty Dictionary>
; > (send z :addAll '((a 1) (b 2) (c 3) (d 4))) Quickly add 4 entries
; #<Dictionary: (a 1) (b 2) (c 3) (d 4)>
; > (send z :at 'b) Given a key, returns value
; 2
; > (send z :at 'e :ifAbsent (lambda () "Key Not Found")) Check ":ifAbsent"
; "Key Not Found"
; > (send z :atPut 'b 7) :atPut will change value
; #<Dictionary: (a 1) (b 7) (c 3) (d 4)>
; > (send z :atPut 'e 100) :atPut will create new entries
; #<Dictionary: (a 1) (b 7) (c 3) (d 4) (e 100)>
; > (send z :asBag) Converting to Bag just gives
; #<Bag: 7 100 4 3 1> values
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; THE SEQUENCEABLECOLLECTION CLASS ;
; ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The class SequenceableCollection is, like Collection, an abstract class.
; This is a good thing since who would want to type "SequenceableCollection"
; very often?
;
(defclass SequenceableCollection () nil Collection)
; Some methods can be defined that will work for all subclasses of
; SequenceableCollection. The minimum index value is 0.
(defmethod SequenceableCollection :at (arg) (elt data arg))
(defmethod SequenceableCollection :atPut (arg value)
(setf (elt data arg) value)
self)
(defmethod SequenceableCollection :first () (self :at 0))
(defmethod SequenceableCollection :last () (self :at (1- (send self :size))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; THE ARRAY CLASS ;
; ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The Array class -- implemented using an array.
; Because it has a fixed size, we have to allocate space for it when
; we create it. We will allow for initialization, since :addAll won't work.
; Either : (send Array :new :size 10) for example, to create an array of 10
; entries or (send Array :new :initial (1 2 3 4 5)) for an initialized array.
(defclass Array () nil SequenceableCollection)
(defmethod Array :isnew (&key size initial)
; Size must be specified when creating array
(if size
(setf (send self :data) (make-array size))
(setf (send self :data)
(make-array (length initial) :initial-contents initial))))
; We have to override :collect because (send Array :new) won't work.
; But we can optimize while we are at it.
(defmethod Array :collect (fcn)
(let ((result (send Array :new :size (send self :size))))
(map-into (send result :data) fcn data)
result))
; We also have to override :select, for the same reason
(defmethod Array :select (fcn)
(let ((result
(mapcan (lambda (x)
(when (funcall fcn x)
(list x)))
(coerce (send self :data) 'list))))
(send (send self :class) :new :initial result)))
; Finally, :storeon needs to be changed since :addAll doesn't work for
; arrays.
(defmethod Array :storeon ()
(list 'send
'Array
:new
:initial
(list
'quote
(send self :asList))))
; Test of the Array class:
; > (setq a (send x :asArray)) Make Array from Set x
; #<Array: 5 4 2 1>
; > (send a :atPut 1 10) Change an element
; #<Array: 5 10 2 1>
; > (send a :select #'evenp) Get array of even elements
; #<Array: 10 2>
; > (send a :collect #'1+) Make array with values 1 larger
; #<Array: 6 11 3 2>
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; THE ORDEREDCOLLECTION CLASS ;
; ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The OrderedCollection class uses linked lists and doesn't have the
; allocation problems of Array.
; Adding or deleteing from the start alters the index numbers,
; so we need a new instance variable to hold the offset.
(defclass OrderedCollection (offset) nil SequenceableCollection)
(defmethod OrderedCollection :isnew (&optional (offset 0))
; Optional argument sets offset of first element.
; This is a "private" feature to aid storeon.
(setf (send self :offset) offset))
; :at, :atPut, :first, and :last need revision
(defmethod OrderedCollection :at (arg) (elt data (+ arg offset)))
(defmethod OrderedCollection :atPut (arg value)
(setf (elt data (+ arg offset)) value)
self)
(defmethod OrderedCollection :first () (car data))
(defmethod OrderedCollection :last () (car (last data)))
; We need to implement add and remove for both ends
; :add will be equivalent to :addLast
(defmethod OrderedCollection :add (arg)
(setq data (nconc data (list arg)))
self)
(defmethod OrderedCollection :addlast (arg) (send self :add arg))
(defmethod OrderedCollection :addFirst (arg)
(setq offset (1+ offset))
(setq data (cons arg data))
self)
(defmethod OrderedCollection :removeFirst ()
(unless (zerop (length data))
(setq offset (1- offset))
(prog1 (car data) (setq data (cdr data)))))
(defmethod OrderedCollection :removeLast ()
(prog1 (car (last data)) (setq data (nbutlast data))))
; Finally, storeon is modified so that offset will be set
(defmethod OrderedCollection :storeon ()
(list 'send
(if (zerop offset)
(list 'send
(intern (send (send self :class) :pname))
:new)
(list 'send
(intern (send (send self :class) :pname))
:new
offset))
:addAll
(list
'quote
(send self :asList))))
; Example of use of OrderedCollection:
; > (setq c (send a :asOrderedCollection)) Make one from Array a
; #<OrderedCollection: 5 10 2 1>
; > (send c :at 1) Value at index 1 is 10
; 10
; > (send c :addFirst 7) Add to front of collection
; #<OrderedCollection: 7 5 10 2 1>
; > (send c :at 1) Index 1 is same spot
; 10
; > (send c :removeLast) Remove from either end
; 1
; > (send c :last) Last element is now 2
; 2
; > (send c :asArray) Convert back to an array
; #<Array: 7 5 10 2>
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; THE SORTEDCOLLECTION CLASS ;
; ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The SortedCollection class requires a sort function. The collection gets
; re-sorted whenever a new element is added. This is a subclass of
; OrderedCollection.
; Offset won't change for this class.
(defclass SortedCollection (sortfcn) nil OrderedCollection)
(defmethod SortedCollection :isnew (&optional (fcn '< ))
(setq sortfcn fcn)
(send-super :isnew))
(defmethod SortedCollection :selfSort ()
; "private" method that sorts the list
(setq data (sort data sortfcn))
self)
(defmethod SortedCollection :add (arg)
(send-super :add arg)
(send self :selfSort))
; Don't allow addFirst, addLast, removefirst, removelast, or atPut
(defmethod SortedCollection :addFirst (arg)
(send self :notImplemented :addFirst))
(defmethod SortedCollection :addLast (arg)
(send self :notImplemented :addLast))
(defmethod SortedCollection :removeFirst (arg)
(send self :notImplemented :removeFirst))
(defmethod SortedCollection :removeLast (arg)
(send self :notImplemented :removeLast))
(defmethod SortedCollection :atPut (arg1 arg2)
(send self :notImplemented :atPut))
; We need a way to remove elements from a Sorted Collection.
; :remove (specifying the element) will do just fine.
(defmethod SortedCollection :remove (arg)
(let ((pos (position arg data))) ; Find (first) instance
(when pos ; Delete found element
(if (zerop pos)
(setq data (cdr data))
(setf (cdr (nthcdr (1- pos) data))
(nthcdr (1+ pos) data))))
self))
; Finally, storeon is modified so that the sort function will be set
(defmethod SortedCollection :storeon ()
(list 'send
(list 'send
(intern (send (send self :class) :pname))
:new
(list
'quote
sortfcn))
:addAll
(list
'quote
(send self :asList))))
; Let's see how the SortedCollection works:
; > (setq s (send c :asSortedCollection)) Sorted when it is created
; #<SortedCollection: 2 5 7 10>
; > (send s :add 8) :add puts new element in order
; #<SortedCollection: 2 5 7 8 10>
; > (send s :asSortedCollection #'>) New collection with order reversed
; #<SortedCollection: 10 8 7 5 2>
; > (send (send (send Set :new) :addAll '(5 3 8 2 5 4 8)) :asSortedCollection)
; #<SortedCollection: 2 3 4 5 8> Eliminate duplicates and sort
; > (send * :asList)
; (2 3 4 5 8)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; ;
; THE INTERVAL CLASS ;
; ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The Interval class is considerably different than the others in that it has
; no "data" but calculates each elements value. We'll use three new
; instance variables -- start end and step. The :new function will take two
; or three arguments to specify start, end and step, with the step size
; defaulting to 1 not defined. We will set the data instance
; variable to T if the range is valid, and to NIL if not (no elements)
(defclass Interval (start end step) nil SequenceableCollection)
(defmethod Interval :isnew (arg1 arg2 &optional arg3)
(if arg3
(setq data (or (and (<= arg1 arg2)(> arg3 0))
(and (>= arg1 arg2)(< arg3 0))))
(setq arg3 1
data (<= arg1 arg2)))
(setq start arg1 end arg2 step arg3)
; Correct End value if necessary
(unless (zerop (rem (- end start) step))
(setq end (- end (rem (- end start) step))))
)
; :at calculates value. We won't check for out of range.
(defmethod Interval :at (arg) (+ start (* step arg)))
; :atPut isn't allowed
(defmethod Interval :atPut (arg1 arg2) (send self :notImplemented :atPut))
; :size returns calculated size
(defmethod Interval :size () (if data (1+ (truncate (- end start) step)) 0))
; :includes must be calcuated
(defmethod Interval :includes (arg)
(cond
((null data) nil)
((> step 0) (and (>= arg start)
(<= arg end)
(zerop (rem (- arg start) step))))
(t (and (<= arg start)
(>= arg end)
(zerop (rem (- arg start) step))))))
; While Collection bases :asList on :map, we want to base :map on
; :asList
(defmethod Interval :map (fcn) (mapcar fcn (send self :asList)))
(defmethod Interval :asList ()
(let ((result nil))
(when data
(dotimes (i (send self :size))
(setq result (cons (+ start (* i step))
result))))
(nreverse result)))
; Since :do is used often with an Interval, and since the default method
; would create a list of values, it would make sense to reimplement :do
; here as an Interval method. That will be left as an exercise for the
; reader!
; :collect, :select will work because we will redefine :species to
; create an OrderedCollection rather than an Interval
(defmethod Interval :species () OrderedCollection)
; Override printing methods
(defmethod Interval :prin1 (&optional (stream *standard-output*))
(format stream
"#<~a from ~s to ~s by ~s>"
(send (send self :class) :pname)
start end step))
; Override :storeon -- this one becomes really easy
(defmethod Interval :storeon ()
(list 'send 'Interval :new start end step))
; A few examples of the use of the Interval class:
; > (setq i (send Interval :new 2 10 2)) Make an interval, i
; #<Interval from 2 to 10 by 2>
; > (send i :do (lambda (x) (format t "~s " x))) Demonstrate :do
; 2 4 6 8 10
; nil
; > (send i :at 3) Check operation of :at
; 8
; > (send i :size) Size of interval
; 5
; > (send i :asList) Convert to a list
; (2 4 6 8 10)
; > (send i :asSortedCollection #'>) Convert to a SortedCollection
; #<SortedCollection: 10 8 6 4 2> sequence changes!
(in-package "USER") ; revert to default package
(use-package "CLASSES") ; Make the classes package accessable
|