Функциональный способ создания массива чисел - PullRequest
0 голосов
/ 01 мая 2018

Как я мог бы написать следующий код более функционально, используя ES6, без каких-либо сторонних библиотек?

// sample pager array
// * output up to 11 pages
// * the current page in the middle, if page > 5
// * don't include pager < 1 or pager > lastPage
// * Expected output using example:
//     [9,10,11,12,13,14,15,16,17,18,19]

const page = 14 // by example
const lastPage = 40 // by example
const pagerPages = page => {
  let newArray = []
  for (let i = page - 5; i <= page + 5; i++) {
    i >= 1 && i <= lastPage ? newArray.push(i) : null
  }
  return newArray
}

Я бы хотел избежать Array.push и, возможно, цикла for, но я не уверен, как мне этого добиться в этой ситуации.

Ответы [ 3 ]

0 голосов
/ 01 мая 2018

Функциональное программирование не ограничено reduce, filter и map; это о функциях. Это означает, что нам не нужно полагаться на извращенные знания, такие как Array.from ({ length: x }), где объект со свойством length можно рассматривать как массив. Такое поведение вызывает недоумение для начинающих и накладные расходы для всех остальных. Думаю, вам понравится писать программы, которые более четко кодируют ваши намерения.

reduce начинается с 1 или более значений и уменьшается до (обычно) одного значения. В этом случае вы действительно хотите реверс из reduce (или fold), здесь называемый unfold. Разница в том, что мы начинаем с одного значения и расширяем или раскрываем его (обычно) несколько значений.

Начнем с упрощенного примера, alphabet. Мы начинаем развертывание с начального значения 97, код символа для буквы a. Мы прекращаем разворачиваться, когда код символа превышает 122, код символа для буквы z.

const unfold = (f, initState) =>
  f ( (value, nextState) => [ value, ...unfold (f, nextState) ]
    , () => []
    , initState
    )

const alphabet = () =>
  unfold
    ( (next, done, char) =>
        char > 122
          ? done ()
          : next ( String.fromCharCode (char) // value to add to output
                 , char + 1                   // next state
                 )
    , 97 // initial state
    )
    
console.log (alphabet ())
// [ a, b, c, ..., x, y, z ]

Выше мы используем одно целое число для нашего состояния, но для других развертываний может потребоваться более сложное представление. Ниже мы показываем классическую последовательность Фибоначчи, разворачивая составное начальное состояние [ n, a, b ], где n - уменьшающийся счетчик, а a и b - числа, используемые для вычисления терминов последовательности. Это демонстрирует, что unfold может использоваться с любым состоянием семени, даже массивами или объектами.

const fib = (n = 0) =>
  unfold
    ( (next, done, [ n, a, b ]) =>
        n < 0
          ? done ()
          : next ( a                   // value to add to output
                 , [ n - 1, b, a + b ] // next state
                 )
    , [ n, 0, 1 ] // initial state
    )

console.log (fib (20))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765 ]

Теперь у нас есть уверенность, чтобы написать pagination. Опять же, наше начальное состояние - составные данные [ page, count ], так как нам нужно отслеживать добавляемое page и сколько страниц (count) мы уже добавили.

Еще одним преимуществом этого подхода является то, что вы можете легко параметризовать такие вещи, как 10 или -5 или +1, и есть разумная семантическая структура для их размещения.

const unfold = (f, initState) =>
  f ( (value, nextState) => [ value, ...unfold (f, nextState) ]
    , () => []
    , initState
    )
    
const pagination = (totalPages, currentPage = 1) =>
  unfold
    ( (next, done, [ page, count ]) =>
        page > totalPages
          ? done ()
          : count > 10
            ? done ()
            : next (page, [ page + 1, count + 1 ])
    , [ Math.max (1, currentPage - 5), 0 ]
    )

console.log (pagination (40, 1))
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]

console.log (pagination (40, 14))
// [ 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 ]

console.log (pagination (40, 38))
// [ 33, 34, 35, 36, 37, 38, 39, 40 ]

console.log (pagination (40, 40))
// [ 35, 36, 37, 38, 39, 40 ]

Выше есть два условия, которые приводят к вызову done (). Мы можем свернуть их, используя ||, и код выглядит немного лучше

const pagination = (totalPages, currentPage = 1) =>
  unfold
    ( (next, done, [ page, count ]) =>
        page > totalPages || count > 10
          ? done ()
          : next (page, [ page + 1, count + 1 ])
    , [ Math.max (1, currentPage - 5), 0 ]
    )
0 голосов
/ 01 мая 2018

Существует много способов создать массив функционально, но создание массива в зависимости от некоторых взаимосвязанных элементов, таких как математические ряды, в основном выполняется путем развертывания. Вы можете рассмотреть разворачивание как обратную сторону уменьшения. Ваш случай не обязательно требует раскрытия, но только для правильного функционального программирования давайте посмотрим, как это можно сделать.

JS не имеет встроенной функции развертывания, но мы можем просто реализовать ее. Прежде всего, как выглядит функция unfold ..?

Array.unfold = function(p,f,t,v){
  var res = [],
   runner = d =>  p(d,res.length,res) ? [] : (res.push(f(d)),runner(t(d)), res);
  return runner(v);
};

Как видно, требуется 4 аргумента.

  1. p: Это функция обратного вызова, такая же, как у нас в программе Reduce. Он вызывается с текущим начальным элементом e для обработки перед вставкой, его индекс i для вставки и доступный в настоящее время массив a как p(e,i,a). Когда он возвращает true, операция развертывания завершается и возвращает созданный массив.
  2. f: Это функция, которую мы будем применять для каждого элемента, который будет построен. Он принимает единственный аргумент, который является текущим значением итерации. Вы можете рассмотреть итерацию значения, например, значения индекса, но мы можем контролировать его итерацию.
  3. t: Это функция, к которой мы будем применять итеративное значение и получать следующее итеративное значение. Для итераций, подобных индексу, это должно быть x => x+1.
  4. v: Великолепное начальное значение.

Пока все хорошо. Как мы собираемся использовать unfold для достижения этой работы. Прежде всего, давайте найдем наше начальное значение с помощью функции, которая принимает в качестве аргумента страницу.

var v = (pg => pg - 5 > 0 ? pg - 5 : 1)(page)

Как насчет функции p, которая решает, где остановиться?

var p = (_,i) => i > 10

Мы будем увеличивать страницы одну за другой, но если у нас есть значение больше lastpage, нам нужно вместо этого указывать нулевые значения. Так что f может выглядеть как

var f = (lp => v => v > lp ? null : v)(lastpage)

и, наконец, t - это функция увеличения значения итерации. Это x => x + 1.

Array.unfold = function(p,f,t,v){
  var res = [],
   runner = d =>  p(d,res.length,res) ? [] : (res.push(f(d)),runner(t(d)), res);
  return runner(v);
};

var v = pg => pg - 5 > 0 ? pg - 5 : 1,
    p = (_,i) => i > 10,
    f = lp => v => v > lp ? null : v,
    t = x => x + 1,
    a = Array.unfold(p,f(40),t,v(14));

console.log(a);
0 голосов
/ 01 мая 2018
  const pageRange = (lastPage, page) => ((start, end) => Array.from({length: end - start + 1}, (_,i) => i + start))(Math.max(1, page - 5), Math.min(lastPage, page + 5));
 const newArray = pageRange(40, 14);

Это чисто функциональный подход. Он использует Math.max/min для достижения границ, а затем использует IIFE для передачи этих границ в Array.from, что создаст массив из end - start элементов, и каждый из этих элементов будет позицией в массиве, увеличенной на начальное значение.


PS: IMO, ваш код на самом деле гораздо более лаконичен (кроме той ненужной троицы) и гораздо более читабелен, чем mys, просто говоря ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...