Browse code
feat(transducers): add DATA-LENS.TRANSDUCERS:INTO, with tests
Edward authored on 09/01/2021 11:19:38
Showing 4 changed files
Showing 4 changed files
... | ... |
@@ -18,7 +18,8 @@ |
18 | 18 |
#:compress-runs #:combine-matching-lists #:sorted #:applicable-when |
19 | 19 |
#:of-length #:of-min-length #:of-max-length #:transform-head |
20 | 20 |
#:maximizing #:zipping #:applying #:splice-elt #:transform-elt #:denest |
21 |
- #:op #:defalias #:<> #:<>1 #:== #:• #:suffixp #:functionalize)) |
|
21 |
+ #:op #:defalias #:<> #:<>1 #:== #:• #:suffixp #:functionalize |
|
22 |
+ #:inc)) |
|
22 | 23 |
|
23 | 24 |
(defpackage :data-lens.transducers.internals |
24 | 25 |
(:use :cl) |
... | ... |
@@ -29,11 +30,11 @@ |
29 | 30 |
(:use :cl) |
30 | 31 |
(:import-from :data-lens.transducers.internals |
31 | 32 |
#:unwrap #:init #:reduce-generic #:stepper #:transduce |
32 |
- #:exit-early) |
|
33 |
+ #:exit-early #:into) |
|
33 | 34 |
(:export #:mapping :filtering :deduping :catting :splitting |
34 | 35 |
#:exit-early :taking :dropping :transduce |
35 | 36 |
#:hash-table-builder :vector-builder :list-builder |
36 | 37 |
#:collecting #:mv-mapping #:mv-selecting |
37 | 38 |
#:hash-table-select #:mv-filtering #:mapcatting |
38 | 39 |
#:lazy-sequence #:compressing-runs #:iota |
39 |
- #:repeating #:repeating*)) |
|
40 |
+ #:repeating #:repeating* #:into)) |
... | ... |
@@ -195,3 +195,65 @@ |
195 | 195 |
(funcall (data-lens:• (data-lens:sorted '< :key 'car) |
196 | 196 |
(data-lens:sorted '< :key 'cdr)) |
197 | 197 |
(alexandria:hash-table-alist result)))))) |
198 |
+ |
|
199 |
+(5am:def-test into (:suite :data-lens.transducers :depends-on mapping) |
|
200 |
+ (5am:is (equal '(0 1 2) |
|
201 |
+ (data-lens.transducers:into '() |
|
202 |
+ (data-lens.transducers:taking 3) |
|
203 |
+ (data-lens.transducers:iota)))) |
|
204 |
+ |
|
205 |
+ (5am:is (equal '(0 1 2) |
|
206 |
+ (data-lens.transducers:into '(0 1 2) |
|
207 |
+ (data-lens.transducers:taking 0) |
|
208 |
+ (data-lens.transducers:iota)))) |
|
209 |
+ |
|
210 |
+ (5am:is (equal '() |
|
211 |
+ (data-lens.transducers:into '() |
|
212 |
+ (data-lens.transducers:taking 0) |
|
213 |
+ (data-lens.transducers:iota)))) |
|
214 |
+ |
|
215 |
+ (5am:is (equal '() |
|
216 |
+ (data-lens.transducers:into '() |
|
217 |
+ (data-lens.transducers:mapping #'identity) |
|
218 |
+ #()))) |
|
219 |
+ |
|
220 |
+ (5am:is (equalp (alexandria:plist-hash-table '(:p 0 :l 1 :i 2 :s 3 :t 4)) |
|
221 |
+ (let ((count 0)) |
|
222 |
+ (data-lens.transducers:into (make-hash-table) |
|
223 |
+ (data-lens.transducers:mapping |
|
224 |
+ (lambda (it) |
|
225 |
+ (prog1 (list it count) |
|
226 |
+ (incf count)))) |
|
227 |
+ '(:p :l :i :s :t))))) |
|
228 |
+ |
|
229 |
+ (loop for type in '(vector list) |
|
230 |
+ do (5am:is (equalp #(1 2 3 4 5 6) |
|
231 |
+ (data-lens.transducers:into #(1 2 3) |
|
232 |
+ (data-lens.transducers:mapping |
|
233 |
+ (data-lens:inc 4)) |
|
234 |
+ (coerce #(0 1 2) type)))) |
|
235 |
+ (5am:is (equal '(1 2 3 4 5 6) |
|
236 |
+ (data-lens.transducers:into '(1 2 3) |
|
237 |
+ (data-lens.transducers:mapping |
|
238 |
+ (data-lens:inc 4)) |
|
239 |
+ (coerce #(0 1 2) type)))) |
|
240 |
+ |
|
241 |
+ (5am:is (equal '(1 2 3 4 5 6) |
|
242 |
+ (data-lens.transducers:into '(1 2 3) |
|
243 |
+ (data-lens.transducers:mapping |
|
244 |
+ (data-lens:inc 4)) |
|
245 |
+ (coerce #(0 1 2) type)))) |
|
246 |
+ (5am:is (equal '(1 2 3 4 5 6) |
|
247 |
+ (data-lens.transducers:into '(1 2 3) |
|
248 |
+ (data-lens:• |
|
249 |
+ (data-lens.transducers:taking 3) |
|
250 |
+ (data-lens.transducers:mapping |
|
251 |
+ (data-lens:inc 4))) |
|
252 |
+ (data-lens.transducers:iota)))) |
|
253 |
+ (5am:is (equalp #(1 2 3 4 5 6) |
|
254 |
+ (data-lens.transducers:into #(1 2 3) |
|
255 |
+ (data-lens:• |
|
256 |
+ (data-lens.transducers:taking 3) |
|
257 |
+ (data-lens.transducers:mapping |
|
258 |
+ (data-lens:inc 4))) |
|
259 |
+ (data-lens.transducers:iota)))))) |
... | ... |
@@ -16,10 +16,19 @@ |
16 | 16 |
seq) |
17 | 17 |
acc))) |
18 | 18 |
|
19 |
+#+(or) |
|
20 |
+(defun document (&rest strings) |
|
21 |
+ (serapeum:string-join strings #.(format nil "~2%"))) |
|
22 |
+ |
|
19 | 23 |
(defgeneric init (client)) |
20 | 24 |
(defgeneric stepper (client)) |
21 | 25 |
(defgeneric unwrap (client obj) |
22 | 26 |
(:method (client obj) obj)) |
27 |
+(defgeneric builder-for-input (seq) |
|
28 |
+ (:documentation |
|
29 |
+ "Take a transducible sequence, return a builder and an init value for that builder. |
|
30 |
+ |
|
31 |
+CONSTRAINT: SEQ should be copied, not modified")) |
|
23 | 32 |
|
24 | 33 |
(defun exit-early (acc) |
25 | 34 |
(throw 'done acc)) |
... | ... |
@@ -36,10 +45,29 @@ |
36 | 45 |
transducer |
37 | 46 |
(init build))))))) |
38 | 47 |
|
39 |
-#+(or) |
|
48 |
+(defun into (to xf from) |
|
49 |
+ (multiple-value-bind (builder init) (builder-for-input to) |
|
50 |
+ (let* ((xf (etypecase xf |
|
51 |
+ (list (apply 'alexandria:compose xf)) |
|
52 |
+ ((or function symbol) xf))) |
|
53 |
+ (transducer (funcall xf (stepper builder)))) |
|
54 |
+ (unwrap builder |
|
55 |
+ (funcall transducer |
|
56 |
+ (catch 'done |
|
57 |
+ (reduce-generic from |
|
58 |
+ transducer |
|
59 |
+ init))))))) |
|
60 |
+ |
|
61 |
+(defmacro defdocumentation (name &body doc-specs) |
|
62 |
+ name doc-specs |
|
63 |
+ nil) |
|
64 |
+ |
|
40 | 65 |
(defdocumentation transducer-protocol |
41 |
- (:function transduce (xf build seq) |
|
42 |
- ) |
|
66 |
+ (:function transduce (xf builder seq) |
|
67 |
+ "Run a transducer XF over sequence SEQ using BUILDER to accumulate results. |
|
68 |
+ |
|
69 |
+Uses the generic function REDUCE-GENERIC so transducers work over lazy |
|
70 |
+sequences and hash tables.") |
|
43 | 71 |
(:generic-function unwrap (client obj) |
44 | 72 |
) |
45 | 73 |
(:generic-function unwrap (client obj) |
... | ... |
@@ -155,6 +155,9 @@ |
155 | 155 |
(destructuring-bind (k v) next |
156 | 156 |
(setf (gethash k acc) v)) |
157 | 157 |
acc))) |
158 |
+(defmethod data-lens.transducers.internals:builder-for-input ((inp hash-table)) |
|
159 |
+ (values 'hash-table-builder |
|
160 |
+ (alexandria:copy-hash-table inp))) |
|
158 | 161 |
|
159 | 162 |
(defmethod init ((it (eql 'vector-builder))) |
160 | 163 |
(make-array 0 :fill-pointer t :adjustable t)) |
... | ... |
@@ -163,6 +166,12 @@ |
163 | 166 |
((acc next) |
164 | 167 |
(vector-push-extend next acc) |
165 | 168 |
acc))) |
169 |
+(defmethod data-lens.transducers.internals:builder-for-input ((inp vector)) |
|
170 |
+ (values 'vector-builder |
|
171 |
+ (make-array (array-dimensions inp) |
|
172 |
+ :initial-contents inp |
|
173 |
+ :fill-pointer t))) |
|
174 |
+ |
|
166 | 175 |
|
167 | 176 |
(defmethod init ((it (eql 'list-builder))) |
168 | 177 |
(declare (optimize (speed 3))) |
... | ... |
@@ -180,6 +189,13 @@ |
180 | 189 |
acc))) |
181 | 190 |
(defmethod unwrap ((it (eql 'list-builder)) obj) |
182 | 191 |
(cdr (elt obj 0))) |
192 |
+(defmethod data-lens.transducers.internals:builder-for-input ((inp list)) |
|
193 |
+ (let ((builder 'list-builder)) |
|
194 |
+ (values builder |
|
195 |
+ (if inp |
|
196 |
+ (let ((inp (cons nil (copy-list inp)))) |
|
197 |
+ (vector inp (last inp))) |
|
198 |
+ (init builder))))) |
|
183 | 199 |
|
184 | 200 |
(defmacro comment (&body body) |
185 | 201 |
(declare (ignore body)) |