В качестве упражнения в функциональном программировании я решил пройти один из своих проектов и заменить функции, содержащие циклы for, на Array.prototype функции высшего порядка , такие как map и reduce .

Одна функция в моем проекте усредняет столбцы в двумерном массиве. Он принимает аргументы samples, который представляет собой двумерный массив размером [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]
  ])
)

Выходными данными должен быть массив размером LOOKBACK, элементы которого являются средними для каждого столбца:

[0.8, 1.75, -0.65]

Я потратил некоторое время, пытаясь найти решение для этого, но я не могу придумать решение.

Возможно ли это с помощью встроенных в Javascript функций Array?

*Обновить

Получил элегантное решение от Кирилла. Если у кого-то есть хорошее решение, я бы хотел увидеть больше.

2
Rocky 2 Мар 2018 в 07:24

3 ответа

Лучший ответ

Попробуйте этот пример с функциями reduce и 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);

Вот более хитрый метод с транспонированием матрицы:

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 Мар 2018 в 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);

Это будет работать даже для другого размера внутреннего массива. Примерно так [[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 Мар 2018 в 05:52

Вступление..

Функциональное программирование - это больше, чем написание однострочников и использование функций высшего порядка , таких как Arary#map, Array#reduce и Array#filter. Кстати, Array#forEach не работает, поскольку не является чистой функцией ..

В дополнение к функциям высшего порядка вы можете использовать каррирование , композиция функций и more .

Алгоритм

Что мы должны сделать, это:

  1. Переставить матрицу
  2. Рассчитать среднее для каждого массива внутри матрицы

Это может выглядеть в JavaScript как:

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

pipe - это функция для объединения нескольких функций в одну огромную функцию. averageChange теперь принимает один аргумент, и он будет проходить через канал.

Перестановка

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

Это выглядит действительно загадочно. Благодаря карри и функциональному составу мы можем переписать его:

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

rearrange - рекурсивная функция, которая преобразует матрицу из

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

Кому

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

Пример рабочего кода

Я написал гораздо больше кода, чем другие решения, но я делю логику на свои собственные функции, что означает, что теперь мы можем использовать функцию, подобную average, для других частей нашего кода. Кроме того, я написал каррированные версии для Array#map и т. Д., Чтобы составить их. Если вы используете библиотеку, это было бы излишним.

// 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 Мар 2018 в 12:30