git.fiddlerwoaroof.com
Browse code

feat(transducers): add DATA-LENS.TRANSDUCERS:INTO, with tests

Edward authored on 09/01/2021 11:19:38
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))