Javascript 使用ramda.js将命令式转换为函数式

Javascript 使用ramda.js将命令式转换为函数式,javascript,functional-programming,ramda.js,Javascript,Functional Programming,Ramda.js,我正在编写使用命令式样式将数字数组转换为新数据列表的代码,但我想使用类似于ramdajs的javascript库将其转换为函数式样式 代码背景 假设美元价值总共有5枚硬币,25美元,20美元。。。1美元。我们得把钱换成美元硬币。用最少的硬币 const data = [25, 20, 10, 5, 1]; const fn = n => data.map((v) => { const numberOfCoin = Number.parseInt(n / v, 10);

我正在编写使用命令式样式将数字数组转换为新数据列表的代码,但我想使用类似于ramdajs的javascript库将其转换为函数式样式

代码背景 假设美元价值总共有5枚硬币,25美元,20美元。。。1美元。我们得把钱换成美元硬币。用最少的硬币

const data = [25, 20, 10, 5, 1];
const fn = n => data.map((v) => {
    const numberOfCoin = Number.parseInt(n / v, 10);
    const result = [v, numberOfCoin];
    n %= v;
    return numberOfCoin ? result : [];
  }).filter(i => i.length > 0);
此代码的结果应该是

fn(48) => [[25, 1], [20, 1], [1, 3]]
fn(100) => [[25, 4]]

我认为您已经有了一个很好的开始,但为了使它更实用,我需要改变一些事情:

使用表达式而不是语句,例如不返回 不要改变数据,例如:否n%=v 您不一定需要Ramda来完成此任务:

常量硬币=值=> [25,20,10,5,1]。减少[acc,val],cur=> 瓦尔console.logcoins100 我认为您已经有了一个很好的开始,但为了使它更实用,我需要改变一些事情:

使用表达式而不是语句,例如不返回 不要改变数据,例如:否n%=v 您不一定需要Ramda来完成此任务:

常量硬币=值=> [25,20,10,5,1]。减少[acc,val],cur=> 瓦尔 地图没有意义,因为它会产生一对一的结果;如果我有4种类型的硬币,我总是会收到4种类型的零钱,这当然不是我们想要的。过滤器的使用迫使您进行更多的处理以达到预期的结果。 reduce可以消除由map+filter引起的中间值,但同样,我们有可能在分析每枚硬币之前达到预期的结果。在fn100返回[[25,4]]的例子中,甚至不需要查看硬币20、10、5或1,因为结果已经达到;进一步削减将是浪费。 对我来说,函数式编程就是为了方便。如果我没有一个能满足我需要的函数,我只需要创建它,因为我的程序清楚地传达它的意图是很重要的。有时这意味着使用更适合我正在处理的数据的结构-

const change = (coins = [], amount = 0) =>

  loop                             // begin a loop, initializing:
    ( ( acc = []                   // an empty accumulator, acc
      , r = amount                 // the remaining amount to make change for, r
      , [ c, ...rest ] = coins     // the first coin, c, and the rest of coins
      ) =>

        r === 0                    // if the remainder is zero
          ? acc                    // return the accumulator

      : c <= r                     // if the coin is small enough
          ? recur                              // recur with
              ( [ ...acc, [ c, div (r, c) ] ]  // updated acc
              , mod (r, c)                     // updated remainder
              , rest                           // rest of coins
              )

                                   // otherwise (inductive) coin is too large
      : recur                      // recur with
          ( acc                    // unmodified acc
          , r                      // unmodified remainder
          , rest                   // rest of coins
          )

    )
在下面自己的浏览器中验证结果-

常数div=x,y=> 数学。圆x/y 常数mod=x,y=> x%y 常量重复=…值=> {重复,值} 常量循环=f=> {let acc=f 而acc&&acc.recur===recur acc=f…acc.值 返回acc } 常量更改=硬币=[],金额=0=> 环 acc=[] ,r=金额 ,[c,…剩余]=硬币 => r==0 ? 行政协调会 :c人们通常会伸手去拿地图、过滤和缩小,但结果往往是有点像是一个方钉在圆孔里

地图没有意义,因为它会产生一对一的结果;如果我有4种类型的硬币,我总是会收到4种类型的零钱,这当然不是我们想要的。过滤器的使用迫使您进行更多的处理以达到预期的结果。 reduce可以消除由map+filter引起的中间值,但同样,我们有可能在分析每枚硬币之前达到预期的结果。在fn100返回[[25,4]]的例子中,甚至不需要查看硬币20、10、5或1,因为结果已经达到;进一步削减将是浪费。 对我来说,函数式编程就是为了方便。如果我没有一个能满足我需要的函数,我只需要创建它,因为我的程序清楚地传达它的意图是很重要的。有时这意味着使用更适合我正在处理的数据的结构-

const change = (coins = [], amount = 0) =>

  loop                             // begin a loop, initializing:
    ( ( acc = []                   // an empty accumulator, acc
      , r = amount                 // the remaining amount to make change for, r
      , [ c, ...rest ] = coins     // the first coin, c, and the rest of coins
      ) =>

        r === 0                    // if the remainder is zero
          ? acc                    // return the accumulator

      : c <= r                     // if the coin is small enough
          ? recur                              // recur with
              ( [ ...acc, [ c, div (r, c) ] ]  // updated acc
              , mod (r, c)                     // updated remainder
              , rest                           // rest of coins
              )

                                   // otherwise (inductive) coin is too large
      : recur                      // recur with
          ( acc                    // unmodified acc
          , r                      // unmodified remainder
          , rest                   // rest of coins
          )

    )
在下面自己的浏览器中验证结果-

常数div=x,y=> 数学。圆x/y 常数mod=x,y=> x%y 常量重复=…值=> {重复,值} 常量循环=f=> {let acc=f 而acc&&acc.recur===recur acc=f…acc.值 返回acc } 常量更改=硬币=[],金额=0=> 环 acc=[] ,r=金额 ,[c,…剩余]=硬币 => r==0 ? 行政协调会
:c这个来自用户633183的答案的变体将找到最小数量的硬币,它不使用通常的技术,即选择每个较大面额的最大数量,而可能以选择更多的硬币为代价

请注意,这可能涉及比原始答案或customcommander的初始答案更多的计算

这里的change返回一个硬币值列表,因此对于58,它将返回[25,25,5,1,1,1]。makeChange将其转换为[[25,2]、[5,1]、[1,3]]格式。如果将minLength函数更改为 长度 金额===0 ? [] :c==1 ? Arrayamount.fill1 :c[ [ 20, 2 ] ] console.log makeChange硬币,45 //=> [ [ 25, 1 ], [ 20, 1 ] ] console.log兑换硬币,48 //=> [ [ 25, 1 ], [ 20, 1 ], [ 1, 3 ] ] console.log makeChange硬币,100 //=> [ [ 25, 4 ] ]
const{pipe,countBy,identity,toPairs,map,sort,down,head}=R这个来自用户633183的答案的变体将找到最小数量的硬币,它没有使用常见的技术,即选择每个较大面额的最大数量,而可能以选择更多硬币为代价

请注意,这可能涉及比原始答案或customcommander的初始答案更多的计算

这里的change返回一个硬币值列表,因此对于58,它将返回[25,25,5,1,1,1]。makeChange将其转换为[[25,2]、[5,1]、[1,3]]格式。如果将minLength函数更改为 长度 金额===0 ? [] :c==1 ? Arrayamount.fill1 :c[[20,2]] console.log makeChange硬币,45 //=> [ [ 25, 1 ], [ 20, 1 ] ] console.log兑换硬币,48 //=> [ [ 25, 1 ], [ 20, 1 ], [ 1, 3 ] ] console.log makeChange硬币,100 //=> [ [ 25, 4 ] ]

const{pipe,countBy,identity,toPairs,map,sort,down,head}=R你能解释一下这个算法吗?输入和输出有什么关系?另外,您提供的代码已经可以使用了,它不是必须的。@VLAZ很抱歉,我忘了解释它的背景。我刚刚编辑了这篇文章。请注意,无论是这个代码还是@customcommander的答案都没有给出最少的硬币数量。40=25+10+5对40=20+20。它仍然是一个有用的算法,它是经常进行更改的方式,但它与描述不符。你是对的。恐怕我完全忽略了这一点。谢谢。@aloebys你能澄清一下wrt Scott的评论吗?你能解释一下算法吗?输入和输出是如何关联的?另外,您提供的代码已经可以使用了,它不是必须的。@VLAZ很抱歉,我忘了解释它的背景。我刚刚编辑了这篇文章。请注意,无论是这个代码还是@customcommander的答案都没有给出最少的硬币数量。40=25+10+5对40=20+20。它仍然是一个有用的算法,它是经常进行更改的方式,但它与描述不符。你是对的。恐怕我完全忽略了这一点。谢谢。@aloebys你能澄清一下Scott的评论吗?我觉得ramda的版本更可读,但是YMMV。一个吹毛求疵的地方:虽然reduce肯定比先映射再过滤要快,但后者以组合reduce所没有的方式传达了代码分离关注点的意图。我觉得让OP知道他们最初的尝试并不遥远是很重要的。话虽如此,我当然承认非Ramda函数感觉更复杂。就我个人而言,我也会使用Ramda并编写函数。我认为产生40=20+20情况的这些变化可能涉及到必要的计算。很可能有一个更优雅的递归,但这不会改变执行的计算数量。一个吹毛求疵的地方:虽然reduce肯定比先映射再过滤要快,但后者以组合reduce所没有的方式传达了代码分离关注点的意图。我觉得让OP知道他们最初的尝试并不遥远是很重要的。话虽如此,我当然承认非Ramda函数感觉更复杂。就我个人而言,我也会使用Ramda并编写函数。我认为产生40=20+20情况的这些变化可能涉及到必要的计算。很可能有更优雅的递归,但这不会改变执行的计算数量。我认为这里的递归部分是最清晰的答案。。。但前提是目标是40=25+10+5,而不是我上面提到的40=20+20。如果是后者,那么我认为需要一种完全不同的方法。谢谢你的反馈,斯科特。我对答案的其他部分并不完全满意,但改进它们的成本超过了我认为改进将产生的额外影响。40=20+20确实要困难得多。如果这是OP需要的,我很乐意扩展答案。我认为这里的递归部分是最清晰的答案。。。但前提是目标是40=25+10+5,而不是我上面提到的40=20+20。如果是后者,那么我认为需要一种完全不同的方法。谢谢你的反馈,斯科特。我对答案的其他部分并不完全满意,但改进它们的成本超过了我认为改进将产生的额外影响。40=20+20确实要困难得多。如果这是OP需要的,我很乐意扩展答案。