Como ejercicio de programación funcional, decidí pasar por uno de mis proyectos y reemplazar las funciones que contienen bucles por <{X0}} ' funciones de orden superior como map y reduce.

Una función en mi proyecto promedia columnas en una matriz bidimensional. Toma un argumento samples que es una matriz 2d de tamaño [n][LOOKBACK]:

[
    [0.6,  4.0, -0.5],
    [1.0, -0.5, -0.8],
    ...
]
const LOOKBACK = 3

function averageChange(samples) {
  let result = []
  let count = 0,
    i, j

  for (i = 0; i < LOOKBACK; i++) {

    let accumulator = 0

    for (j = 0; j < samples.length; j++) {
      accumulator += samples[j][i]
    }

    result.push(accumulator / samples.length)
  }

  return result
}

console.log(
  averageChange([
    [0.6, 4.0, -0.5],
    [1.0, -0.5, -0.8]
  ])
)

El resultado debe ser una matriz de tamaño LOOKBACK cuyos elementos son el promedio de cada columna:

[0.8, 1.75, -0.65]

He pasado algún tiempo tratando de encontrar una solución para esto, pero parece que no puedo encontrar una.

¿Es esto posible usar las funciones integradas de matriz de Javascript?

*Actualizar

Tengo una solución elegante de Kirill. Si alguien más tiene una buena solución, me encantaría ver más.

2
Rocky 2 mar. 2018 a las 07:24

3 respuestas

La mejor respuesta

Pruebe este ejemplo con las funciones reduce y forEach:

let a = [
    [0.6,  4.0, -0.5],
    [3.0, -0.5, -0.1],
    [1.0, -0.2, -0.8],
    [7.0, -0.5, -0.8]
];

let b = a.reduce((acc, cur) => {
    cur.forEach((e, i) => acc[i] = acc[i] ? acc[i] + e : e);
    return acc;
}, []).map(e => e / a.length);

console.log(b);

Aquí está el método más astuto con transposición de matriz:

let a = [
    [0.6,  4.0, -0.5],
    [3.0, -0.5, -0.1],
    [1.0, -0.2, -0.8],
    [7.0, -0.5, -0.8]
];

let b = a[0].map((col, i) => a.map(row => row[i]).reduce((acc, c) => acc + c, 0) / a.length);

console.log(b);
2
Kirill Simonov 2 mar. 2018 a las 05:59
var a=[];var b=[];
var i = 0; j = 0;

cubes.forEach(function each(item,length) {
  if (Array.isArray(item)) {
    item.forEach(each);
    j++;
    i = 0;
  } else {
    if(a[i]===undefined){
        a[i]=0;b[i]=0}
    a[i]=(a[i]*b[i]+item)/(b[i]+1);
    b[i]=b[i]+1;
    i++;
  }
});
console.log(a);

Esto funcionará incluso para diferentes tamaños de matriz interna. Algo como esto [[1], [2,3,4]]

cubes=[[1],[1,2,3]]
var a=[];var b=[];
var i = 0; j = 0;

cubes.forEach(function each(item,length) {
  if (Array.isArray(item)) {
    item.forEach(each);
    j++;
    i = 0;
  } else {
    if(a[i]===undefined){
        a[i]=0;b[i]=0}
    a[i]=(a[i]*b[i]+item)/(b[i]+1);
    b[i]=b[i]+1;
    i++;
  }
});
console.log(a);
0
yajiv 2 mar. 2018 a las 05:52

Introducción ..

La programación funcional es más que escribir one-liners y usar funciones de orden superior como Arary#map, Array#reduce y Array#filter. Por cierto, Array#forEach no es funcional porque no es una función pura ..

Además de las funciones de orden superior , puede usar curry , composición de funciones , y más .

Algoritmo

Lo que tenemos que hacer es:

  1. Reorganizar la matriz
  2. Calcule el promedio de cada matriz dentro de la matriz

Esto podría verse en JavaScript como:

const averageChange = pipe(
    rearrange ([]),
    map (average) 
)

pipe es una función para componer múltiples funciones en una gran función. averageChange toma ahora un argumento y este fluirá a través de la tubería.

Reorganizar

const rearrange = yss => xss => 
    xss[0].length === 0
        ? yss
        : rearrange
            (concat (yss) ([ map ( getIndex (0) ) ( xss ) ]))
            (map ( slice (1, xss[0].length) ) ( xss ))

Esto se ve realmente críptico. Gracias al curry y la composición funcional podemos reescribirlo:

const rearrange = yss => xss => 
    matrixLength (xss) === 0
        ? yss
        : rearrange
            (concat (yss) ([ firstIndeces ( xss ) ]))
            (excludeFirstIndeces ( xss ))

rearrange es una función recursiva que transforma la matriz de

[
    [0.6,  4.0, -0.5],
    [3.0, -0.5, -0.1],
    [1.0, -0.2, -0.8],
    [7.0, -0.5, -0.8]
]

Para

[ 
    [ -0.5, -0.1, -0.8, -0.8 ],
    [  4  , -0.5, -0.2, -0.5 ],
    [  0.6,  3  ,  1  ,  7   ] 
]

Ejemplo de código de trabajo

He escrito mucho más código que las otras soluciones, pero divido la lógica en mis propias funciones, lo que significa que ahora podemos usar funciones como average para otras partes de nuestro código. Además, he escrito versiones con curry para Array#map etc. para componerlas. Si usa una biblioteca, sería superfluo.

// helper functions
const pipe = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)))

const getIndex = i => xs => 
    xs[i]

const map = f => xs =>
    xs.map(f)

const reduce = f => seel => xs =>
    xs.reduce(f)

const concat = ys => xs =>
    xs.concat(ys)

const slice = (start, end) => xs =>
    xs.slice(start, end)

const average = xs =>
    reduce ((sum, x) => sum + x) (0) (xs) / xs.length
    
const length = xs =>
  xs.length
    
const matrixLength = pipe(
  getIndex(0),
  length
)

const firstIndex = getIndex (0)

const firstIndeces = map ( firstIndex )

const excludeFirstIndex = xss => slice (1, matrixLength (xss)) (xss)

const excludeFirstIndeces = map ( excludeFirstIndex )
  
   
// business logic 
const rearrange = yss => xss => 
    matrixLength (xss) === 0
        ? yss
        : rearrange
            (concat (yss) ([ firstIndeces ( xss ) ]))
            (excludeFirstIndeces ( xss ))

const averageChange = pipe (
    rearrange ([]),
    map(average) 
)

const values = [
    [0.6,  4.0, -0.5],
    [3.0, -0.5, -0.1],
    [1.0, -0.2, -0.8],
    [7.0, -0.5, -0.8]
]

console.log( averageChange (values) )
2
Roman 2 mar. 2018 a las 12:30