cl-slice
cl-slice copied to clipboard
Array slices for Common Lisp
#+CAPTION: Project Status: Abandoned – Initial development has started, but there has not yet been a stable, usable release; the project has been abandoned and the author(s) do not intend on continuing development. [[http://www.repostatus.org/badges/latest/abandoned.svg]]
This repository is archived. You may find an updated version of these libraries at https://github.com/Lisp-Stat/select
- Array slices for Common Lisp
This library provides the following:
-
A user interface for taking slices (elements selected by the Cartesian product of vectors of subscripts for each axis) of array-like objects. The most important function is =slice=. Unless you want to define a method for this (besides what is already implemented), this is pretty much all you need from this library. See the next section for a tutorial.
-
An extensible DSL for selecting a subset of valid subscripts. This is useful if, for example, you want to resolve column names in a data frame in your implementation of =slice=.
-
A set of utility functions for traversing slices in array-like objects.
- User interface
The most frequently used form is #+BEGIN_SRC lisp (slice object slice1 slice2 ...) #+END_SRC Each /slice/ selects a subset of subscripts along the corresponding axis. The library supports the following slice specifications:
-
a nonnegative integer selects the corresponding index, while a negative integer selects an index counting backwards from the last index: #+BEGIN_SRC lisp (slice #(0 1 2 3) 1) ; => 1
(slice #(0 1 2 3) -2) ; => 2
#+END_SRC These are called /singleton slices/. Each singleton slice /drops/ the dimension: vectors become atoms, matrices become vectors, etc. -
=(cons start end)= selects integers $i: \text{start} \leq i < \text{end}$. When =end= is =nil=, the last index is included (cf. =subseq=). Each boundary is resolved according to the other rules if applicable, so you can use negative integers: #+BEGIN_SRC lisp (slice #(0 1 2 3) (cons 1 3)) ; => #(1 2) (slice #(0 1 2 3) (cons 1 -1)) ; => #(1 2) #+END_SRC However, =(cons start end)= is /invalid unless/ $\text{start} < \text{end}$, so you can't use #+BEGIN_SRC lisp (slice #(0 1 2 3) (cons 2 2)) ; ERROR #+END_SRC
-
=t= selects all subscripts: #+BEGIN_SRC lisp (slice #2A((0 1 2) (3 4 5)) t 1) ; => #(1 4) #+END_SRC
-
vectors concatenate selections (except for bit vectors): #+BEGIN_SRC lisp (slice #(0 1 2 3 4 5 6 7 8 9) (vector (cons 1 3) 6 (cons -2 -1))) ; => #(1 2 3 6 8 9) (slice #(0 1 2) #(2 2 1 0 0)) ; => #(2 2 1 0 0) #+END_SRC
-
bit vectors can be used as a mask: #+BEGIN_SRC lisp (slice #(0 1 2 3 4) #*00110) ; => #(2 3) #+END_SRC
This is pretty much the core functionality --- the semantics can be extended, see the next section. The following extensions are provided by the library.
-
=including= is convenient if you want the slice to include the end of the range: #+BEGIN_SRC lisp (slice #(0 1 2 3) (including 1 2)) ; => #(1 2), cf (slice ... (cons 1 3)) #+END_SRC
-
=nodrop= is useful if you don't want to drop dimensions: #+BEGIN_SRC lisp (slice #(0 1 2 3) (nodrop 2)) ; => #(2), cf (slice ... (cons 2 3)) #+END_SRC
-
=head= and =tail= do the obvious: #+BEGIN_SRC lisp (slice #(0 1 2 3) (head 2)) ; => #(0 1) (slice #(0 1 2 3) (tail 2)) ; => #(2 3) #+END_SRC
All of these are trivial to implement --- if there is something you are missing, you can easily extend =slice=. You may also want to submit a patch!
=ref= is a version of slice that always returns a single element, so it can only be used with singleton slices.
- Slice semantics
Arguments of =slice= (apart from the first one) are meant to be resolved using =canonical-representation=, in the =cl-slice-dev= package. If you want to extend =slice=, you should define methods for =canonical-representation=. See the documentation for details, here I provide a simple example that extends the semantics with ordinal numbers.
#+BEGIN_SRC lisp (defmacro define-ordinal-slice (number) (check-type number (integer 0)) `(defmethod cl-slice-dev:canonical-representation ((axis integer) (slice (eql ',(intern (format nil "~:@(~:r~)" number))))) (assert (< ,number axis)) (cl-slice-dev:canonical-singleton ,number)))
(define-ordinal-slice 1) (define-ordinal-slice 2) (define-ordinal-slice 3)
(slice #(0 1 2 3 4 5) (cons 'first 'third)) ; => #(1 2) #+END_SRC
Note the following:
- The value returned by =canonical-representation= needs to be constructed using =canonical-singleton=, =canonical-range=, or =canonical-sequence=. You should not use the internal representation directly as it is subject to change.
- You can assume that =axis= is an integer: this is the default. An object may define a more complex mapping (such as, for example, named rows & columns), but unless a method specialized to that is found, =canonical-representation= will just query its dimension (with =axis-dimension=) and try to find a method that works on integers.
- You need to make sure that the subscript is valid, hence the assertion.
- Traversing slices
** TODO write this
- Reporting bugs
Please report bugs using the [[https://github.com/tpapp/cl-slice/issues][issue tracker]].