【问题标题】:Functional Programming: Sum of properties函数式编程:属性总和
【发布时间】:2016-04-02 03:10:40
【问题描述】:

我正在尝试在 JS using Ramda 中实现一个函数,该函数接受一个对象列表并返回特定属性的总和。例如

var l = [
  {a: 1, b: 2, c:  0},
  {a: 1, b: 3, c: -1},
  {a: 1, b: 4, c:  0},
]
func(['a', 'b'], l)
-> {a: 3, b: 9}

原则上,我需要这样的函数:

R.map(R.props($1, _), $2)

在函数式编程中实现这样的东西最优雅的方式是什么? R.map(R.props) 不工作的原因很明显。我尝试使用R.composeR.pipe 的一些组合,但我没有运气

【问题讨论】:

  • b 属性不应该等于 9 吗?
  • 你完全正确!抱歉,我修好了。

标签: javascript functional-programming ramda.js


【解决方案1】:

我会把它分成两部分:

const fnOverProp = R.curry((fn, prop, list) => fn(R.pluck(prop, list)));

const fnOverProps = R.curry((fn, props, list) => 
    R.fromPairs(R.zip(props)(R.map(fnOverProp(fn, __, list), props))));

(对不起,我在这里命名时遇到了创意障碍。这些名字非常糟糕。)

你可以这样使用它:

fnOverProp(R.sum, 'b', list); //=> 9
fnOverProps(R.sum, ['a', 'b'], list); //=> {a: 3, b: 9}
const sumOverProps = fnOverProps(R.sum);
sumOverProps(['a', 'c'], list); //=> {a: 3, c: -1}

首先请注意,我将您的想法概括为使sum 成为参数。这对我来说是有道理的,这不是人们可能想要对这样一个功能做的唯一事情。

然后我将它分解为一个对单个属性名称进行操作的函数。这让我觉得它本身就很有用。您可能不需要对它们的整个列表执行此操作,并且此功能值得单独使用。

然后我将其包装在一个函数中,该函数将第一个函数映射到一个属性列表。请注意,这实际上是一个相当简单的函数:

(fn, props, list) => R.map(fnOverProp(fn, R.__, list), props)

包装在两个包装器中,以将结果的平面列表转换为您正在寻找的对象输出。 R.zip(props)(<thatFn>, props)[3, -1] 转换为[['a', 3], ['c', -1]],然后R.fromPairs 将其转换为{a: 3, c: -1}

这不会为您提供您想要的单行实现。显然,您可以将第一个函数的定义折叠到第二个函数中,但我认为这不会给您带来太多好处。即使它可以无积分,我希望在这种情况下这只会降低可读性。

您可以在 Ramda REPL 中看到这一点。

【讨论】:

    【解决方案2】:

    这也可以定义为对象列表的reducer。给定结果的一些初始状态,我们需要一个函数,可以将两个对象属性的结果相加,其中props 是我们感兴趣的属性列表:

    reduce(useWith(mergeWith(add, [identity, pick(props)]))
    

    然后,您有两种选择,即列表是潜在非空的还是保证至少有一个对象。如果保证列表不为空,reducer 的初始值可以简单地作为列表的头部,将尾部作为列表进行迭代。

    const func = (props, objs) =>
      reduce(useWith(mergeWith(add), [identity, pick(props)]), pick(props, head(objs)), tail(objs))
    

    但是,如果列表可能为空,则必须使用空值(在这种情况下为零)初始化 reduce 函数。

    const func = (props, objs) =>
      reduce(useWith(mergeWith(add), [identity, pick(props)]), pick(props, map(flip(objOf)(0), props)), objs)
    

    【讨论】:

      【解决方案3】:

      你可以试试这个:

      function add(a, b){  
         return Object.keys(a).reduce(function(p, c){
           p[c] += a[c];
           return p;
         }, b);
      }
      
      console.log(R.reduce(add, {a:0, b:0, c:0}, l))
      

      这是另一种方法:

      R.pipe(R.map(R.props(['a', 'b', 'c'])), R.transpose, R.map(R.sum))(l);
      

      【讨论】:

      • 这肯定是一个正确的解决方案,但它不是很优雅。理想情况下,我正在寻找一行来表达我想要计算的内容。我还想将解决方案应用于类似情况下可能出现的其他问题。
      • 第二种单行表达方式,希望对您有所帮助。
      • 谢谢,我正在寻找更多类似R.pipe(R.map(R.props), R.transpose, R.map(R.sum))(['a', 'b', 'c'], l) 的东西。做这样的事情的诀窍实际上是我还不理解的函数式编程中的概念。
      • 啊,R.useWith 看起来很有用。我快到了:R.pipe(R.useWith(R.map, [R.props, R.identity]), R.transpose, R.map(R.sum)) (['a', 'b'], [{a:1,b:2}, {a:2,b:2}])
      猜你喜欢
      • 2010-09-06
      • 2021-05-26
      • 1970-01-01
      • 1970-01-01
      • 2012-11-02
      • 2018-09-20
      • 2017-06-18
      • 2021-11-18
      • 2021-09-10
      相关资源
      最近更新 更多