I waited for a week to see what would happen and then I bought a Kinder egg.
Just bought.
It has a yellow thing. Coincidence?
Surprise!
SEND
+ MORE
------
MONEY
CPU | GC | CPU | GC | ||
---|---|---|---|---|---|
literal | 2648.8 | 58.8 | 11051.0 | 174.6 |
#lang racket
(define digits '(0 1 2 3 4 5 6 7 8 9))
(define (to-number digits)
(foldl {lambda (x y) (+ (* 10 y) x)} 0 digits))
(time (for*/list ([s (remove* (list 0) digits)]
[e (remove* (list s) digits)]
[n (remove* (list s e) digits)]
[d (remove* (list s e n) digits)]
[send (in-value (to-number (list s e n d)))]
[m (remove* (list 0 s e n d) digits)]
[o (remove* (list s e n d m) digits)]
[r (remove* (list s e n d m o) digits)]
[more (in-value (to-number (list m o r e)))]
[y (remove* (list s e n d m o r) digits)]
[money (in-value (to-number (list m o n e y)))]
#:when (= (+ send more) money))
(list send more money)))
CPU | GC | CPU | GC | ||
---|---|---|---|---|---|
literal | 2648.8 | 58.8 | 11051.0 | 174.6 | |
in-list | 2377.4 | 15.4 | 9185.4 | 62.6 |
(time (for*/list ([s (in-list (remove* (list 0) digits))]
[e (in-list (remove* (list s) digits))]
[n (in-list (remove* (list s e) digits))]
[d (in-list (remove* (list s e n) digits))]
[send (in-value (to-number (list s e n d)))]
[m (in-list (remove* (list 0 s e n d) digits))]
[o (in-list (remove* (list s e n d m) digits))]
[r (in-list (remove* (list s e n d m o) digits))]
[more (in-value (to-number (list m o r e)))]
[y (in-list (remove* (list s e n d m o r) digits))]
[money (in-value (to-number (list m o n e y)))]
#:when (= (+ send more) money))
(list send more money)))
CPU | GC | CPU | GC | ||
---|---|---|---|---|---|
in-list | 2377.4 | 15.4 | 9185.4 | 62.6 | |
= | 1507.0 | 28.2 | 5559.8 | 62.8 |
(time (for*/list ([s (in-list (remove* (list 0) digits =))]
[e (in-list (remove* (list s) digits =))]
[n (in-list (remove* (list s e) digits =))]
[d (in-list (remove* (list s e n) digits =))]
[send (in-value (to-number (list s e n d)))]
[m (in-list (remove* (list 0 s e n d) digits =))]
[o (in-list (remove* (list s e n d m) digits =))]
[r (in-list (remove* (list s e n d m o) digits =))]
[more (in-value (to-number (list m o r e)))]
[y (in-list (remove* (list s e n d m o r) digits =))]
[money (in-value (to-number (list m o n e y)))]
#:when (= (+ send more) money))
(list send more money)))
CPU | GC | CPU | GC | ||
---|---|---|---|---|---|
= | 1507.0 | 28.2 | 5559.8 | 62.8 | |
#:when | 1766.0 | 40.8 | 6071.8 | 187.4 |
(time (for*/list ([s (in-list digits)]
#:when (not (ormap {lambda (x) (= x s)} (list 0)))
[e (in-list digits)]
#:when (not (ormap {lambda (x) (= x e)} (list s)))
[n (in-list digits)]
#:when (not (ormap {lambda (x) (= x n)} (list s e)))
[d (in-list digits)]
#:when (not (ormap {lambda (x) (= x d)} (list s e n)))
[send (in-value (to-number (list s e n d)))]
[m (in-list digits)]
#:when (not (ormap {lambda (x) (= x m)} (list 0 s e n d)))
[o (in-list digits)]
#:when (not (ormap {lambda (x) (= x o)} (list s e n d m)))
[r (in-list digits)]
#:when (not (ormap {lambda (x) (= x r)} (list s e n d m o)))
[more (in-value (to-number (list m o r e)))]
[y (in-list digits)]
#:when (not (ormap {lambda (x) (= x y)} (list s e n d m o r)))
[money (in-value (to-number (list m o n e y)))]
#:when (= (+ send more) money))
(list send more money)))
(define-syntax m:ormap
(syntax-rules (list)
[(m:ormap proc (list n ...)) (let ([proc-val proc])
(or (proc-val n) ...))]))
As m:ormap is a macro, it can do strange things, like spying the expressions in
the parameters positions and see that the second is a explicit list with the
pattern (list ...) and then
apply directly the proc function to each of the elements, without creating the
list.(define-syntax m:ormap/bad
(syntax-rules (list)
[(m:ormap proc (list n ...)) (or (proc n) ...)]))
(ormap (begin (display "*") {lambda (x) (display x) #f}) (list 1 2 3))
(m:ormap (begin (display "*") {lambda (x) (display x) #f}) (list 1 2 3))
(m:ormap/bad (begin (display "*") {lambda (x) (display x) #f}) (list 1 2 3))
(newline)
(ormap (let ([r (random 6)]) {lambda (x) (display r) #f}) (list 1 2 3))
(m:ormap (let ([r (random 6)]) {lambda (x) (display r) #f}) (list 1 2 3))
(m:ormap/bad (let ([r (random 6)]) {lambda (x) (display r) #f}) (list 1 2 3))
(newline)
(ormap {lambda (x) (= x n)} (list s e))
is equal to(or (= s n) (= e n))
Similarly, we replace
foldl
with
m:foldl. The definition is a little more
complicated. Moreover, rather than one intermediate variable it creates a lot of
intermediate variables which fortunately disappear (similarly to the
intermediary variable proc-val that disappears in m:ormap).(define-syntax m:foldl
(syntax-rules (list)
[(m:foldl proc ini (list)) ini]
[(m:foldl proc ini (list x y ...)) (let ([proc-val proc])
(m:foldl proc-val (proc-val x ini) (list y ...)))]))
Here is another technical problem if we use m:fold (which is a macro) inside
to-number (which is a function). It turns out that
to-number is inlined after
the expansion of m:fold. So
m:fold doesn't see the explicit list, but it sees
only the argument of to-number. In general, you have to be careful because the
macros that look like functions sometimes don't compose magically with the
actual functions. Therefore we must also make a macro
m:to-number.(define-syntax-rule (m:to-number digits)
(m:foldl {lambda (x y) (+ (* 10 y) x)} 0 digits))
(m:to-number (list s e n d))
to(m:foldl {lambda (x y) (+ (* 10 y) x)} 0 (list s e n d))
without breaking the explicit list and then m:fold sees the explicit list when
it is expanded.CPU | GC | CPU | GC | ||
---|---|---|---|---|---|
#:when | 1766.0 | 40.8 | 6071.8 | 187.4 | |
macros | 271.4 | 0.0 | 951.4 | 0.0 |
(ormap display (list 1 2 3))
to(ormap (display 1) (display 2) (display 3))
is not very difficult. But when the Racket compiler can act,
ormap is already
inlined and the structure that the optimizer sees is more complicated. This is
one of the optimizations that I have on my wishlist and I think at some point it
can be added to Racket.(define-syntax m:ormap
(syntax-rules (list)
[(m:ormap p (list n ...)) (let ([p_ p])
(or (p_ n) ...))]))
(define-syntax m:foldl
(syntax-rules (list)
[(m:foldl p ini (list)) ini]
[(m:foldl p ini (list x y ...)) (let ([p_ p])
(m:foldl p_ (p_ x ini) (list y ...)))]))
(define-syntax-rule (m:to-number digits)
(m:foldl {lambda (x y) (+ (* 10 y) x)} 0 digits))
(time (for*/list ([s (in-list digits)]
#:when (not (m:ormap {lambda (x) (= x s)} (list 0)))
[e (in-list digits)]
#:when (not (m:ormap {lambda (x) (= x e)} (list s)))
[n (in-list digits)]
#:when (not (m:ormap {lambda (x) (= x n)} (list s e)))
[d (in-list digits)]
#:when (not (m:ormap {lambda (x) (= x d)} (list s e n)))
[send (in-value (m:to-number (list s e n d)))]
[m (in-list digits)]
#:when (not (m:ormap {lambda (x) (= x m)} (list 0 s e n d)))
[o (in-list digits)]
#:when (not (m:ormap {lambda (x) (= x o)} (list s e n d m)))
[r (in-list digits)]
#:when (not (m:ormap {lambda (x) (= x r)} (list s e n d m o)))
[more (in-value (m:to-number (list m o r e)))]
[y (in-list digits)]
#:when (not (m:ormap {lambda (x) (= x y)} (list s e n d m o r)))
[money (in-value (m:to-number (list m o n e y)))]
#:when (= (+ send more) money))
(list send more money)))
CPU | GC | CPU | GC | ||
---|---|---|---|---|---|
macros | 271.4 | 0.0 | 951.4 | 0.0 | |
unsafe | 234.0 | 0.0 | 833.2 | 0.0 |
(require racket/unsafe/ops)
(define-syntax-rule (m:unsafe-fx-to-number digits)
(m:foldl {lambda (x y) (unsafe-fx+ (unsafe-fx* 10 y) x)} 0 digits))
(time (for*/list ([s (in-list digits)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x s)} (list 0)))
[e (in-list digits)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x e)} (list s)))
[n (in-list digits)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x n)} (list s e)))
[d (in-list digits)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x d)} (list s e n)))
[send (in-value (m:unsafe-fx-to-number (list s e n d)))]
[m (in-list digits)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x m)} (list 0 s e n d)))
[o (in-list digits)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x o)} (list s e n d m)))
[r (in-list digits)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x r)} (list s e n d m o)))
[more (in-value (m:unsafe-fx-to-number (list m o r e)))]
[y (in-list digits)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x y)} (list s e n d m o r)))
[money (in-value (m:unsafe-fx-to-number (list m o n e y)))]
#:when (unsafe-fx= (unsafe-fx+ send more) money))
(list send more money)))
CPU | GC | CPU | GC | ||
---|---|---|---|---|---|
unsafe | 234.0 | 0.0 | 833.2 | 0.0 | |
in-range | 231.0 | 0.0 | 764.2 | 0.0 |
(time (for*/list ([s (in-range 10)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x s)} (list 0)))
[e (in-range 10)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x e)} (list s)))
[n (in-range 10)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x n)} (list s e)))
[d (in-range 10)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x d)} (list s e n)))
[send (in-value (m:unsafe-fx-to-number (list s e n d)))]
[m (in-range 10)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x m)} (list 0 s e n d)))
[o (in-range 10)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x o)} (list s e n d m)))
[r (in-range 10)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x r)} (list s e n d m o)))
[more (in-value (m:unsafe-fx-to-number (list m o r e)))]
[y (in-range 10)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x y)} (list s e n d m o r)))
[money (in-value (m:unsafe-fx-to-number (list m o n e y)))]
#:when (unsafe-fx= (unsafe-fx+ send more) money))
(list send more money)))
CPU | GC | CPU | GC | ||
---|---|---|---|---|---|
literal | 2648.8 | 58.8 | 11051.0 | 174.6 | |
in-list | 2377.4 | 15.4 | 9185.4 | 62.6 | |
= | 1507.0 | 28.2 | 5559.8 | 62.8 | |
#:when | 1766.0 | 40.8 | 6071.8 | 187.4 | |
macros | 271.4 | 0.0 | 951.4 | 0.0 | |
unsafe | 234.0 | 0.0 | 833.2 | 0.0 | |
in-range | 231.0 | 0.0 | 764.2 | 0.0 |
SEND
+ MORE
------
MONEY
CPU | GC | CPU | GC | ||
---|---|---|---|---|---|
literal | 2648,8 | 58,8 | 11051,0 | 174,6 |
#lang racket
(define digits '(0 1 2 3 4 5 6 7 8 9))
(define (to-number digits)
(foldl {lambda (x y) (+ (* 10 y) x)} 0 digits))
(time (for*/list ([s (remove* (list 0) digits)]
[e (remove* (list s) digits)]
[n (remove* (list s e) digits)]
[d (remove* (list s e n) digits)]
[send (in-value (to-number (list s e n d)))]
[m (remove* (list 0 s e n d) digits)]
[o (remove* (list s e n d m) digits)]
[r (remove* (list s e n d m o) digits)]
[more (in-value (to-number (list m o r e)))]
[y (remove* (list s e n d m o r) digits)]
[money (in-value (to-number (list m o n e y)))]
#:when (= (+ send more) money))
(list send more money)))
CPU | GC | CPU | GC | ||
---|---|---|---|---|---|
literal | 2648,8 | 58,8 | 11051,0 | 174,6 | |
in-list | 2377,4 | 15,4 | 9185,4 | 62,6 |
(time (for*/list ([s (in-list (remove* (list 0) digits))]
[e (in-list (remove* (list s) digits))]
[n (in-list (remove* (list s e) digits))]
[d (in-list (remove* (list s e n) digits))]
[send (in-value (to-number (list s e n d)))]
[m (in-list (remove* (list 0 s e n d) digits))]
[o (in-list (remove* (list s e n d m) digits))]
[r (in-list (remove* (list s e n d m o) digits))]
[more (in-value (to-number (list m o r e)))]
[y (in-list (remove* (list s e n d m o r) digits))]
[money (in-value (to-number (list m o n e y)))]
#:when (= (+ send more) money))
(list send more money)))
CPU | GC | CPU | GC | ||
---|---|---|---|---|---|
in-list | 2377,4 | 15,4 | 9185,4 | 62,6 | |
= | 1507,0 | 28,2 | 5559,8 | 62,8 |
(time (for*/list ([s (in-list (remove* (list 0) digits =))]
[e (in-list (remove* (list s) digits =))]
[n (in-list (remove* (list s e) digits =))]
[d (in-list (remove* (list s e n) digits =))]
[send (in-value (to-number (list s e n d)))]
[m (in-list (remove* (list 0 s e n d) digits =))]
[o (in-list (remove* (list s e n d m) digits =))]
[r (in-list (remove* (list s e n d m o) digits =))]
[more (in-value (to-number (list m o r e)))]
[y (in-list (remove* (list s e n d m o r) digits =))]
[money (in-value (to-number (list m o n e y)))]
#:when (= (+ send more) money))
(list send more money)))
CPU | GC | CPU | GC | ||
---|---|---|---|---|---|
= | 1507,0 | 28,2 | 5559,8 | 62,8 | |
#:when | 1766,0 | 40,8 | 6071,8 | 187,4 |
(time (for*/list ([s (in-list digits)]
#:when (not (ormap {lambda (x) (= x s)} (list 0)))
[e (in-list digits)]
#:when (not (ormap {lambda (x) (= x e)} (list s)))
[n (in-list digits)]
#:when (not (ormap {lambda (x) (= x n)} (list s e)))
[d (in-list digits)]
#:when (not (ormap {lambda (x) (= x d)} (list s e n)))
[send (in-value (to-number (list s e n d)))]
[m (in-list digits)]
#:when (not (ormap {lambda (x) (= x m)} (list 0 s e n d)))
[o (in-list digits)]
#:when (not (ormap {lambda (x) (= x o)} (list s e n d m)))
[r (in-list digits)]
#:when (not (ormap {lambda (x) (= x r)} (list s e n d m o)))
[more (in-value (to-number (list m o r e)))]
[y (in-list digits)]
#:when (not (ormap {lambda (x) (= x y)} (list s e n d m o r)))
[money (in-value (to-number (list m o n e y)))]
#:when (= (+ send more) money))
(list send more money)))
(define-syntax m:ormap
(syntax-rules (list)
[(m:ormap proc (list n ...)) (let ([proc-val proc])
(or (proc-val n) ...))]))
Como m:ormap
es una macro puede hacer cosas más raras, como espiar las expresiones en las
posiciones de los parámetros y ver que el segundo es una lista en forma
explicita de la forma (list
...) y aplicar la función
proc
directamente a cada uno de los elementos sin crear la lista en ningún momento.(define-syntax m:ormap/bad (syntax-rules (list) [(m:ormap proc (list n ...)) (or (proc n) ...)])) (ormap (begin (display "*") {lambda (x) (display x) #f}) (list 1 2 3))
(m:ormap (begin (display "*") {lambda (x) (display x) #f}) (list 1 2 3))
(m:ormap/bad (begin (display "*") {lambda (x) (display x) #f}) (list 1 2 3))
(newline)
(ormap (let ([r (random 6)]) {lambda (x) (display r) #f}) (list 1 2 3))
(m:ormap (let ([r (random 6)]) {lambda (x) (display r) #f}) (list 1 2 3))
(m:ormap/bad (let ([r (random 6)]) {lambda (x) (display r) #f}) (list 1 2 3))
(newline)
(ormap {lambda (x) (= x n)} (list s e))
Queda equivalente a(or (= s n) (= e n))
(define-syntax m:foldl
(syntax-rules (list)
[(m:foldl proc ini (list)) ini]
[(m:foldl proc ini (list x y ...)) (let ([proc-val proc])
(m:foldl proc-val (proc x ini) (list y ...)))]))
Acá hay otro problema técnico si usamos m:fold. (que es una macro) en to-number
(que es una función). Resulta que to-number se inlinea después de la expansión
de m:fold. Así que m:fold no ve la lista explicita sino que ve el argumento de to-number. En general hay que tener cuidado porque las macros que parecen
funciones a veces no se componen mágicamente con las funciones de verdad. Por
eso tenemos que armar también la macro m:to-number. (define-syntax-rule (m:to-number digits)
(m:foldl {lambda (x y) (+ (* 10 y) x)} 0 digits))
(m:to-number (list s e n d))
en(m:foldl {lambda (x y) (+ (* 10 y) x)} 0 (list s e n d))
sin romper la lista explicita y entonces m:fold puede ver la lista explícita
cuando se expande.CPU | GC | CPU | GC | ||
---|---|---|---|---|---|
#:when | 1766,0 | 40,8 | 6071,8 | 187,4 | |
macros | 271,4 | 0,0 | 951,4 | 0,0 |
(ormap display (list 1 2 3))
a(ormap (display 1) (display 2) (display 3))
no parece muy difícil. Pero cuando el compilador de Racket puede actuar ormap ya
está inlineada y la estructura que ve el optimizador es más complicada. Es una
de las optimizaciones que tengo en mi lista de deseos y creo que en algún
momento se puede agregar a Racket.(define-syntax m:ormap
(syntax-rules (list)
[(m:ormap proc (list n ...)) (let ([proc-val proc])
(or (proc-val n) ...))]))
(define-syntax m:foldl
(syntax-rules (list)
[(m:foldl proc ini (list)) ini]
[(m:foldl proc ini (list x y ...)) (let ([proc-val proc])
(m:foldl proc-val (proc x ini) (list y ...)))]))
(define-syntax-rule (m:to-number digits)
(m:foldl {lambda (x y) (+ (* 10 y) x)} 0 digits))
(time (for*/list ([s (in-list digits)]
#:when (not (m:ormap {lambda (x) (= x s)} (list 0)))
[e (in-list digits)]
#:when (not (m:ormap {lambda (x) (= x e)} (list s)))
[n (in-list digits)]
#:when (not (m:ormap {lambda (x) (= x n)} (list s e)))
[d (in-list digits)]
#:when (not (m:ormap {lambda (x) (= x d)} (list s e n)))
[send (in-value (m:to-number (list s e n d)))]
[m (in-list digits)]
#:when (not (m:ormap {lambda (x) (= x m)} (list 0 s e n d)))
[o (in-list digits)]
#:when (not (m:ormap {lambda (x) (= x o)} (list s e n d m)))
[r (in-list digits)]
#:when (not (m:ormap {lambda (x) (= x r)} (list s e n d m o)))
[more (in-value (m:to-number (list m o r e)))]
[y (in-list digits)]
#:when (not (m:ormap {lambda (x) (= x y)} (list s e n d m o r)))
[money (in-value (m:to-number (list m o n e y)))]
#:when (= (+ send more) money))
(list send more money)))
CPU | GC | CPU | GC | ||
---|---|---|---|---|---|
macros | 271,4 | 0,0 | 951,4 | 0,0 | |
unsafe | 234,0 | 0,0 | 833,2 | 0,0 |
(require racket/unsafe/ops)
(define-syntax-rule (m:unsafe-fx-to-number digits)
(m:foldl {lambda (x y) (unsafe-fx+ (unsafe-fx* 10 y) x)} 0 digits))
(time (for*/list ([s (in-list digits)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x s)} (list 0)))
[e (in-list digits)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x e)} (list s)))
[n (in-list digits)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x n)} (list s e)))
[d (in-list digits)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x d)} (list s e n)))
[send (in-value (m:unsafe-fx-to-number (list s e n d)))]
[m (in-list digits)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x m)} (list 0 s e n d)))
[o (in-list digits)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x o)} (list s e n d m)))
[r (in-list digits)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x r)} (list s e n d m o)))
[more (in-value (m:unsafe-fx-to-number (list m o r e)))]
[y (in-list digits)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x y)} (list s e n d m o r)))
[money (in-value (m:unsafe-fx-to-number (list m o n e y)))]
#:when (unsafe-fx= (unsafe-fx+ send more) money))
(list send more money)))
CPU | GC | CPU | GC | ||
---|---|---|---|---|---|
unsafe | 234,0 | 0,0 | 833,2 | 0,0 | |
in-range | 231,0 | 0,0 | 764,2 | 0,0 |
(time (for*/list ([s (in-range 10)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x s)} (list 0)))
[e (in-range 10)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x e)} (list s)))
[n (in-range 10)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x n)} (list s e)))
[d (in-range 10)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x d)} (list s e n)))
[send (in-value (m:unsafe-fx-to-number (list s e n d)))]
[m (in-range 10)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x m)} (list 0 s e n d)))
[o (in-range 10)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x o)} (list s e n d m)))
[r (in-range 10)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x r)} (list s e n d m o)))
[more (in-value (m:unsafe-fx-to-number (list m o r e)))]
[y (in-range 10)]
#:when (not (m:ormap {lambda (x) (unsafe-fx= x y)} (list s e n d m o r)))
[money (in-value (m:unsafe-fx-to-number (list m o n e y)))]
#:when (unsafe-fx= (unsafe-fx+ send more) money))
(list send more money)))
CPU | GC | CPU | GC | ||
---|---|---|---|---|---|
literal | 2648,8 | 58,8 | 11051,0 | 174,6 | |
in-list | 2377,4 | 15,4 | 9185,4 | 62,6 | |
= | 1507,0 | 28,2 | 5559,8 | 62,8 | |
#:when | 1766,0 | 40,8 | 6071,8 | 187,4 | |
macros | 271,4 | 0,0 | 951,4 | 0,0 | |
unsafe | 234,0 | 0,0 | 833,2 | 0,0 | |
in-range | 231,0 | 0,0 | 764,2 | 0,0 |