【问题标题】:Functional way to create an array of numbers创建数字数组的函数式方法
【发布时间】:2018-05-02 06:43:20
【问题描述】:

如何在没有任何第三方库的情况下使用 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 循环,但我不确定在这种情况下如何实现它。

【问题讨论】:

  • new Array((page + 5) - (page - 5)).fill().map((d, i) =&gt; i + (page - 5))
  • @BenM 或 Array.from({length: 10}, (d, i) =&gt; i + (page - 5)).
  • @BenM 不等于 (page + 5) - (page - 5) 总是等于 10?
  • @Mark_M 是的,确实如此。只是为了清楚起见而添加。
  • 你还想过滤掉小于1和大于lastPage的数字,所以基本上:new Array((page + 5) - (page - 5)).fill().map((d, i) =&gt; i + (page - 5)).filter(n =&gt; 1 &lt;= n &amp;&amp; n &lt;= lastPage )

标签: javascript functional-programming


【解决方案1】:

函数式编程不限于reducefiltermap;这是关于功能的。这意味着我们不必依赖像Array.from ({ length: x }) 这样的错误知识,其中具有length 属性的对象可以被视为数组。这种行为对初学者来说是令人困惑的,对其他人来说是精神上的开销。它认为你会喜欢编写更清晰地编码你的意图的程序。

reduce 以 1 个或多个值开始,然后(通常)减少为单个值。在这种情况下,您实际上想要reduce(或fold)的reverse,这里称为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 是递减计数器,ab 是用于计算数列项的数字。这表明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 ]
    )

【讨论】:

  • 另外两个人使用fromfillreducemapfilter 删除了他们的答案
  • we don't have to rely on perverse knowledge ... 哈哈,我想这比你的unfold 回调更容易理解。但是,干得好:)
  • @JonasW。当然,如果你不习惯高阶函数和函数作为普通数据的概念,“回调”会很奇怪。但它是几乎所有现代语言的众所周知的属性。另一方面,Array.from ({ length: x }) 是 Javascript 的一个怪癖(由鸭子打字引起),深深地埋在语言的内部。
  • “反常”可能有点……苛刻?每种语言都有奇怪的极端情况。建议换成深奥?特定语言?用这个奇怪的技巧构建一个数组?
  • @JonasW。我的大部分答案都是针对作者删除的答案。我确实认为以这种方式利用语言的行为是不正当的。尽管可以使输出正确,但该程序是不可读的(即使您承认),因为它是在考虑利用漏洞的情况下构思的。我们的程序应该接受两个数字并构建一个数组,但使用fromfill start 和一个数组来回答(或不正当的“array-like”)和从那里向后工作。
【解决方案2】:
  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 你的代码实际上比我的代码更简洁(除了那些不必要的三元组)并且更具可读性,只是说......

【讨论】:

  • Wups,你的程序坏了 Jonas :( 它产生[9,10,...14,...17,18](缺少19)。它为pageRange(40, 1) 产生[1,2,3,4,5](缺少[6,7,...11])。它产生[35,36,...39] (缺少40)为pageRange(40, 40)
  • @user633183 对,需要包含上边界。然而1 -&gt; [1,2,3,4,5] 似乎是预期的行为,OPs 代码也会产生这种行为
【解决方案3】:

有很多方法可以在功能上创建数组,但根据一些相关项目(如数学系列)创建数组主要是通过展开来完成的。您可以考虑像减少的逆向一样展开。您的案例不一定需要展开,但只是为了正确的函数式编程,让我们看看它是如何完成的。

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 =&gt; x+1
  4. v:是光荣的初始值。

到目前为止一切顺利。我们将如何使用unfold 来完成这项工作。首先让我们通过一个以 page 作为参数的函数来找到我们的初始值。

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 =&gt; 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);

【讨论】:

    猜你喜欢
    • 2020-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-22
    • 1970-01-01
    相关资源
    最近更新 更多