diff --git a/Makefile b/Makefile index 6fb3a4c2..d544c5b0 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ include Makefile.config # Commands -CYCLONE = cyclone +CYCLONE = cyclone -A . CCOMP = $(CC) $(CFLAGS) INDENT_CMD = indent -linux -l80 -i2 -nut @@ -26,7 +26,8 @@ HEADERS = $(HEADER_DIR)/runtime.h $(HEADER_DIR)/types.h TEST_SRC = $(TEST_DIR)/unit-tests.scm \ $(TEST_DIR)/srfi-28-tests.scm \ $(TEST_DIR)/srfi-60-tests.scm \ - $(TEST_DIR)/srfi-121-tests.scm + $(TEST_DIR)/srfi-121-tests.scm \ + $(TEST_DIR)/array-list-tests.scm TESTS = $(basename $(TEST_SRC)) # Primary rules (of interest to an end user) @@ -214,6 +215,10 @@ bootstrap : icyc libs cp scheme/cyclone/test.c $(BOOTSTRAP_DIR)/scheme/cyclone cp scheme/cyclone/test.meta $(BOOTSTRAP_DIR)/scheme/cyclone cp scheme/cyclone/test.scm $(BOOTSTRAP_DIR)/scheme/cyclone + cp scheme/cyclone/array-list.c $(BOOTSTRAP_DIR)/scheme/cyclone + cp scheme/cyclone/array-list.meta $(BOOTSTRAP_DIR)/scheme/cyclone + cp scheme/cyclone/array-list.sld $(BOOTSTRAP_DIR)/scheme/cyclone #just in case + cp scheme/cyclone/array-list.scm $(BOOTSTRAP_DIR)/scheme/cyclone #just in case cp srfi/1.c $(BOOTSTRAP_DIR)/srfi cp srfi/2.c $(BOOTSTRAP_DIR)/srfi cp srfi/2.meta $(BOOTSTRAP_DIR)/srfi diff --git a/docs/API.md b/docs/API.md index 640373c4..1bde97dd 100644 --- a/docs/API.md +++ b/docs/API.md @@ -53,6 +53,7 @@ Cyclone supports the following [Scheme Requests for Implementation (SRFI)](http: These libraries are provided by Cyclone with a stable API that is unlikely to change. +- [`scheme cyclone array-list`](api/scheme/cyclone/array-list.md) - [`scheme cyclone pretty-print`](api/scheme/cyclone/pretty-print.md) - [`scheme cyclone test`](api/scheme/cyclone/test.md) @@ -201,6 +202,13 @@ This section is an alphabetic listing of all the functions, objects, and macros [`append`](api/scheme/base.md#append) [`apply `](api/primitives.md#apply) [`arithmetic-shift`](api/srfi/60.md#ash) +[`array-list`](api/scheme/cyclone/array-list.md#array-list) +[`array-list?`](api/scheme/cyclone/array-list.md#array-list1) +[`array-list-delete!`](api/scheme/cyclone/array-list.md#array-list-delete) +[`array-list-empty?`](api/scheme/cyclone/array-list.md#array-list-empty) +[`array-list-length`](api/scheme/cyclone/array-list.md#array-list-length) +[`array-list-ref`](api/scheme/cyclone/array-list.md#array-list-ref) +[`array-list-set!`](api/scheme/cyclone/array-list.md#array-list-set) [`ash`](api/srfi/60.md#ash) [`asin`](api/scheme/inexact.md#asin) [`assoc`](api/scheme/base.md#assoc) @@ -789,6 +797,7 @@ This section is an alphabetic listing of all the functions, objects, and macros [`macro:load-env!`](api/scheme/cyclone/macros.md#macroload-env) [`macro:macro?`](api/scheme/cyclone/macros.md#macromacro) [`magnitude`](api/scheme/complex.md#magnitude) +[`make-array-list`](api/scheme/cyclone/array-list.md#make-array-list) [`make-bytevector `](api/primitives.md#make-bytevector) [`make-client-socket`](api/srfi/106.md#make-client-socket) [`make-comparator `](api/srfi/128.md#make-comparator) diff --git a/docs/api/scheme/cyclone/array-list.md b/docs/api/scheme/cyclone/array-list.md new file mode 100644 index 00000000..92b84bc7 --- /dev/null +++ b/docs/api/scheme/cyclone/array-list.md @@ -0,0 +1,145 @@ +# Array list library # + +A simple [dynamic array][1] implementation, designed to be used to support +various other structures. + +## Description ## + +The scale factor for this implementation is 2, which is used by several other +languages' standard library implementation of dynamic arrays. The backing +structure is a Scheme vector, with a minimum size of 16 to avoid early resizing +on small structures. + +## Definitions ## + +When describing a procedure's arguments, ``array-list`` denotes an array list +argument. + +## Limitations ## + +Currently does not contain a full set of operations - more will be added if +needed or requested. + +## Contents ## + +- [`array-list`](#array-list) +- [`array-list?`](#array-list1) +- [`array-list-delete!`](#array-list-delete) +- [`array-list-empty?`](#array-list-empty) +- [`array-list-insert!`](#array-list-insert) +- [`array-list-length`](#array-list-length) +- [`array-list-ref`](#array-list-ref) +- [`array-list-set!`](#array-list-set) +- [`make-array-list`](#make-array-list) + +## Constructors ## + +### array-list ### + +```(array-list obj ...)``` + +Creates a new array list containing all of ``obj``, in that order. + +**Time complexity:** *O(n)* (amortized), where *n* is ``(length obj)``. + +**Space complexity:** *O(n)* (worst-case), where *n* is ``(length obj)`` + +### make-array-list ### + +```(make-array-list k)``` + +Creates a new empty array list with a capacity of ``(max 16 k)`` elements. + +**Time complexity:** *O(n)* (worst-case), where *n* is ``k``. + +**Space complexity:** *O(n)* (worst-case), where *n* is ``k``. + +## Predicates ## + +### array-list? ### + +```(array-list? obj)``` + +Returns ``#t`` if ``obj`` is an array list, and ``#f`` otherwise. + +**Time complexity:** *O(1)* (worst-case) + +**Space complexity:** *O(1)* (worst-case) + +```(array-list-empty? array-list)``` + +Returns ``#t`` if ``array-list`` contains no items, and ``#f`` otherwise. + +**Time complexity:** *O(1)* (worst-case) + +**Space complexity:** *O(1)* (worst-case) + +## Accessors ## + +### array-list-length ### + +```(array-list-length array-list)``` + +Returns the number of items stored in ``array-list``. + +**Time complexity:** *O(1)* (worst-case) + +**Space complexity:** *O(1)* (worst-case) + +### array-list-ref ### + +```(array-list-ref array-list k)``` + +Returns the ``k``th element of ``array-list`` (zero-indexed). It is an error if +``k`` is greater than, or equal to, ``(array-list-length array-list)``. + +**Time complexity:** *O(1)* (worst-case) + +**Space complexity:** *O(1)* (worst-case) + +## Mutators ## + +### array-list-delete! ### + +```(array-list-delete! array-list)``` +```(array-list-delete! array-list k)``` + +Deletes the element at position ``k`` in ``array-list`` (zero-indexed). If ``k`` +is not provided, deletes the last element of ``array-list`` instead. Elements at +positions after ``k`` are 'slid over' to fill the 'gap' left by deleting the +element. It is an error if ``k`` is greater than, or equal to +``(array-list-length array-list)``. + +**Time complexity:** *O(n)* (worst-case), *O(1)* (amortized, when deleting last +element) + +**Space complexity:** *O(n)* (worst-case), *O(1)* (amortized) + +### array-list-insert! ### + +```(array-list-insert! array-list obj)``` +```(array-list-insert! array-list k obj)``` + +Inserts ``obj`` at position ``k`` in ``array-list`` (zero-indexed). If ``k`` is +not provided, inserts ``obj`` at the end of ``array-list``. Elements are `slid +over` away from the ``k``th position to create a 'gap' for ``obj`` if necessary. +It is an error if ``k`` is greater than ``(array-list-length array-list)``. + +**Time complexity:** *O(n)* (worst-case), *O(1)* (amortized, when inserting at +the end) + +**Space complexity:** *O(n)* (worst-case), *O(1)* (amortized) + +### array-list-set! ### + +```(array-list-set! array-list k obj)``` + +Replaces the element at position ``k`` in ``array-list`` with ``obj`` +(zero-indexed). It is an error if ``k`` is greater than, or equal to, +``(array-list-length array-list)``. + +**Time complexity:** *O(1)* (worst-case) + +**Space complexity:** *O(1)* (worst-case) + +[1]: https://en.wikipedia.org/wiki/Dynamic_array diff --git a/scheme/cyclone/array-list.scm b/scheme/cyclone/array-list.scm new file mode 100644 index 00000000..c2429e37 --- /dev/null +++ b/scheme/cyclone/array-list.scm @@ -0,0 +1,118 @@ +#| + | Copyright (c) 2017 Koz Ross + | + | Permission is hereby granted, free of charge, to any person obtaining a copy of + | this software and associated documentation files (the "Software"), to deal in + | the Software without restriction, including without limitation the rights to + | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + | the Software, and to permit persons to whom the Software is furnished to do so, + | subject to the following conditions: + | + | The above copyright notice and this permission notice shall be included in all + | copies or substantial portions of the Software. + | + | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + |# + +(define-record-type + + (new-al x y z) + array-list? + (x store set-store!) + (y size set-size!) + (z capacity set-capacity!)) + +(define-syntax with-array-list + (syntax-rules () + ((with-array-list (store-bind size-bind capacity-bind) arr-list body ...) + (let ((store-bind (store arr-list)) + (size-bind (size arr-list)) + (capacity-bind (capacity arr-list))) + (begin + body ...))))) + +(define min-size 16) + +(define (check-positive-integer x) + (unless (and (integer? x) + (positive? x)) + (error "Expected positive integer argument."))) + +(define (check-in-bounds lo hi x) + (unless (and (integer? x) + (>= x lo) + (< x hi)) + (error "Expected positive integer, in bounds."))) + +(define (make-array-list n) + (check-positive-integer n) + (let ((len (max min-size n))) + (new-al (make-vector len) 0 len))) + +(define (refit! al) + (with-array-list + (s len cap) al + (define (resize! x) + (let ((new-al (make-vector x))) + (vector-copy! new-al 0 s 0 len) + (set-store! al new-al) + (set-capacity! al (vector-length new-al)))) + (cond + ((= len cap) (resize! (* 2 cap))) + ((and (> cap min-size) + (< len (/ cap 4))) (resize! (/ cap 2)))))) + +(define array-list-insert! + (case-lambda + ((al x) + (refit! al) + (vector-set! (store al) (size al) x) + (set-size! al (+ 1 (size al)))) + ((al i x) + (check-in-bounds 0 (+ 1 (size al)) i) + (if (= i (size al)) + (array-list-insert! al x) + (begin + (refit! al) + (vector-copy! (store al) (+ 1 i) (store al) i (size al)) + (vector-set! (store al) i x) + (set-size! al (+ 1 (size al)))))))) + +(define array-list-delete! + (case-lambda + ((al) + (refit! al) + (vector-set! (store al) (- (size al) 1) #f) + (set-size! al (- (size al) 1))) + ((al i) + (check-in-bounds 0 (size al) i) + (if (= i (- (size al) 1)) + (array-list-delete! al) + (begin + (refit! al) + (vector-copy! (store al) i (store al) (+ 1 i) (size al)) + (set-size! al (- (size al) 1)))))))` + +(define (array-list . items) + (define len (length items)) + (define al (make-array-list len)) + (for-each (lambda (x) + (array-list-insert! al x))) + al) + +(define (array-list-empty? al) + (zero? (size al))) + +(define (array-list-ref al i) + (vector-ref (store al) i)) + +(define (array-list-length al) + (size al)) + +(define (array-list-set! al i x) + (vector-set! (store al) i x)) diff --git a/scheme/cyclone/array-list.sld b/scheme/cyclone/array-list.sld new file mode 100644 index 00000000..ce4c98d8 --- /dev/null +++ b/scheme/cyclone/array-list.sld @@ -0,0 +1,36 @@ +#| + | Copyright (c) 2017 Koz Ross + | + | Permission is hereby granted, free of charge, to any person obtaining a copy of + | this software and associated documentation files (the "Software"), to deal in + | the Software without restriction, including without limitation the rights to + | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + | the Software, and to permit persons to whom the Software is furnished to do so, + | subject to the following conditions: + | + | The above copyright notice and this permission notice shall be included in all + | copies or substantial portions of the Software. + | + | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + |# + +(define-library + (scheme cyclone array-list) + (import + (scheme base) + (scheme case-lambda)) + (export + array-list array-list? + array-list-delete! + array-list-empty? + array-list-insert! + array-list-length + array-list-ref + array-list-set! + make-array-list) + (include "array-list.scm")) diff --git a/tests/array-list-tests.scm b/tests/array-list-tests.scm new file mode 100644 index 00000000..be7e3f15 --- /dev/null +++ b/tests/array-list-tests.scm @@ -0,0 +1,85 @@ +#| + | Copyright (c) 2016 John Cowan + | Copyright (c) 2017 Koz Ross + | + | Permission is hereby granted, free of charge, to any person obtaining a copy of + | this software and associated documentation files (the "Software"), to deal in + | the Software without restriction, including without limitation the rights to + | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + | the Software, and to permit persons to whom the Software is furnished to do so, + | subject to the following conditions: + | + | The above copyright notice and this permission notice shall be included in all + | copies or substantial portions of the Software. + | + | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + |# + +(import + (scheme base) + (srfi 27) + (scheme cyclone array-list) + (scheme cyclone test)) + +(define test-size 2000) + +(test-group + "array-list/basic" + (define a (make-array-list 10)) + (test 0 (array-list-length a)) + (test-assert (array-list-empty? a)) + (array-list-insert! a 5) + (test 1 (array-list-length a)) + (test-not (array-list-empty? a)) + (test 5 (array-list-ref a 0)) + (array-list-insert! a 0 12) + (test 2 (array-list-length a)) + (test-not (array-list-empty? a)) + (test 12 (array-list-ref a 0)) + (test 5 (array-list-ref a 1)) + (array-list-insert! a 1 15) + (test 3 (array-list-length a)) + (test-not (array-list-empty? a)) + (test 12 (array-list-ref a 0)) + (test 15 (array-list-ref a 1)) + (test 5 (array-list-ref a 2)) + (array-list-set! a 2 45) + (test 3 (array-list-length a)) + (test-not (array-list-empty? a)) + (test 12 (array-list-ref a 0)) + (test 15 (array-list-ref a 1)) + (test 45 (array-list-ref a 2)) + (array-list-delete! a) + (test 2 (array-list-length a)) + (test-not (array-list-empty? a)) + (test 12 (array-list-ref a 0)) + (test 15 (array-list-ref a 1)) + (array-list-delete! a 0) + (test 1 (array-list-length a)) + (test-not (array-list-empty? a)) + (test 15 (array-list-ref a 0))) + +(test-group + "array-list/hammer" + (define al (make-array-list 16)) + (do + ((i 0 (+ i 1))) + ((= i test-size)) + (test i (array-list-length al)) + (array-list-insert! al i) + (test i (array-list-ref al i))) + (do + ((i test-size (- i 1))) + ((= i 2)) + (let* ((r (random-integer (- (array-list-length al) 2))) + (x (array-list-ref al r))) + (test i (array-list-length al)) + (array-list-delete! al r) + (test-not (= x (array-list-ref al r)))))) + +(test-exit)