matrices - como hacer un tablero de ajedrez en javascript
rendimiento de una lista de generadores creados a partir de una matriz (5)
Tengo este generador recursivo
var obj = [1,2,3,[4,5,[6,7,8],9],10]
function *flat(x) {
if (Array.isArray(x))
for (let y of x)
yield *flat(y)
else
yield ''foo'' + x;
}
console.log([...flat(obj)])
Funciona bien, pero no me gusta la parte. ¿Hay alguna manera de escribirlo funcionalmente? Lo intenté
if (Array.isArray(x))
yield *x.map(flat)
que no funcionó.
¿Hay alguna forma de escribir la función anterior sin bucles?
El map
es una buena idea, pero debe reducir la matriz resultante de objetos generadores a un solo objeto generador:
function *flat(x) {
if (Array.isArray(x))
yield *x.map(flat).reduce((a, b) => function*() { yield *a; yield *b }());
else
yield ''foo'' + x;
}
var obj = [1,2,3,[4,5,[6,7,8],9],10];
console.log([...flat(obj)]);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Podría reducir la matriz al generador. Pero esto se ve peor que el lazo para mí (aunque es funcional :))
var obj = [1, 2, 3, [4, 5, [6, 7, 8], 9], 10]
function* flat(x) {
if (Array.isArray(x))
yield * x.reduceRight(
(f, y) => function*() {
yield * flat(y);
yield * f()
},
function*() {}
)()
else
yield ''foo'' + x;
}
console.log([...flat(obj)])
puede ser algo así como
var obj = [1, 2, 3, [4, 5, [6, 7, 8], 9], 10];
function* flat(x) {
if (Array.isArray(x)) {
yield x.map(v => {
return [...flat(v)].join();
});
} else yield "foo" + x;
}
console.log([...flat(obj)]);
¿Hay alguna manera de escribirlo funcionalmente, sin bucles?
No en realidad no. (Por supuesto, siempre puede optar por la recursión, pero cuestionaré la utilidad de ese enfoque).
Lo que estamos buscando son combinadores funcionales para iteradores:
function* of(x) { // also known as `pure` or `return`
yield x;
}
function map(f) { return function* (xs) { // also known as `fmap`
for (const x of xs)
yield f(x);
}
function* join(xss) { // also known as `concat` (not `append`!) or `flatten` (but non-recursive!)
for (const xs of xss)
for (const x of xs)
yield x;
}
function chain(f) { return function* (xs) { // also known as `concatMap` or `bind`
for (const x of xs)
const ys = f(x);
for (const y of ys)
yield y;
}
// or const chain = f => compose(concat, map(f)) :-)
Ahora podemos tratar los iteradores como una mónada y no preocuparnos más por la implementación.
Como puede ver, no he usado la sintaxis yield* xs
arriba que es (básicamente) solo azúcar para
for (const x of xs)
yield x;
Lo que se ve extraño en su implementación es la disparidad entre el bucle externo y el no bucle interno. En un mundo óptimo, habría una sintaxis de yield**
que hacía lo que join
hace, pero no hay. Así que solo podemos implementar su función muy bien con las funciones auxiliares anteriores:
function* flat(x) {
if (Array.isArray(x))
yield* chain(flat)(x);
else
yield* of(''foo'' + x); // foreshadowing
}
o solo
function flat(x) {
return Array.isArray(x) ? chain(flat)(x) : of(''foo'' + x);
}
Puede usar parámetros de reposo ...
y verificar la longitud de la matriz de reposo para otra llamada del generador
function* flat(a, ...r) {
if (Array.isArray(a)) {
yield* flat(...a);
} else {
yield ''foo'' + a;
}
if (r.length) {
yield* flat(...r);
}
}
var obj = [1, 2, 3, [4, 5, [6, 7, 8], 9], 10];
console.log([...flat(obj)])
.as-console-wrapper { max-height: 100% !important; top: 0; }
Un enfoque similar pero con un generador de spread
para llamar al generador entregado con los valores de propagación.
function* spread(g, a, ...r) {
yield* g(a);
if (r.length) {
yield* spread(g, ...r);
}
}
function* flat(a) {
if (Array.isArray(a)) {
yield* spread(flat, ...a);
} else {
yield ''foo'' + a;
}
}
var obj = [1, 2, 3, [4, 5, [6, 7, 8], 9], 10];
console.log([...flat(obj)])
.as-console-wrapper { max-height: 100% !important; top: 0; }