【问题标题】:map add/reduce two array object with same index映射添加/减少具有相同索引的两个数组对象
【发布时间】:2018-06-15 01:37:12
【问题描述】:

我有两个数组对象如下:

var arr1 = [
    {
        name: 1,
        value: 10
    },
    {
        name: 2,
        value: 15
    }
]

var arr2 = [
    {
        name: 3,
        value: 5
    },
    {
        name: 4,
        value: 3
    }
]

我想重新定义key,并用相同的索引减少每个数据。

输出:

var arr1 = [
    {
        itemLabel: 1,
        itemValue: 5
    }, 
    {
        itemLabel: 2,
        itemValue: 12
    }
]

我现在做的如下:

formatData = arr1.map((row, index) => ({
    itemLabel: arr1.name,
    itemValue: arr1.value - arr2[index].value
}))

有没有更好的解决方案?

【问题讨论】:

  • 您当前的解决方案有什么问题?您所说的“更实用”是什么意思?
  • 我认为他的意思是,“有没有更好的解决方案”
  • 您的map 调用已经是一个纯函数。它不会改变任何现有对象或导致任何副作用。它不能更实用。你可以用另一种方式来做,但不会更好。
  • 你做的是更好的解决方案
  • mm,我明白了。有没有更好的解决方案!?

标签: javascript arrays json ecmascript-6 functional-programming


【解决方案1】:

你的代码很好,你也可以使用递归:

var arr1 =[{
  name: 1,
  value: 10 
}, {
  name: 2,
  value: 15
}];

var arr2= [{
  name: 3,
  value: 5 
}, {
  name: 4,
  value: 3
}]


const createObject=(arr1,arr2,ret=[])=>{
  if(arr1.length!==arr2.length){
    throw("Arrays should be the same length.")
  }
  const item = {
    itemLabel: arr1[0].name,
    itemValue: arr1[0].value - arr2[0].value
  };
  if(arr1.length===0){
    return ret;
  };
  return createObject(arr1.slice(1),arr2.slice(1),ret.concat(item));
}
console.log(createObject(arr1,arr2));

实现mapreduce 的两个函数都必须在其范围之外使用arr1 或arr2(不作为参数传递给它),所以严格来说不是纯粹的。但是您可以通过部分应用轻松解决它:

var arr1 =[{
  name: 1,
  value: 10 
}, {
  name: 2,
  value: 15
}];

var arr2= [{
  name: 3,
  value: 5 
}, {
  name: 4,
  value: 3
}];


const mapFunction = arr2 => (item,index) => {
  return {
    itemLabel: item.name,
    itemValue: item.value - arr2[index].value
  }
}

var createObject=(arr1,arr2,ret=[])=>{
  if(arr1.length!==arr2.length){
    throw("Arrays should be the same length.")
  }
  const mf = mapFunction(arr2);
  return arr1.map(mf);
}
console.log(createObject(arr1,arr2));

但正如 CodingIntrigue 在评论中提到的那样:这些都没有比你已经做的“更好”了。

【讨论】:

    【解决方案2】:

    要使您的解决方案更实用,您需要将匿名函数更改为纯(匿名)函数。

    纯函数是一个函数,给定相同的输入,总是返回相同的输出

    匿名函数依赖于可变变量arr1arr2。这意味着它取决于系统状态。所以它不符合纯函数规则。

    以下可能不是最好的实现,但我希望它能给你一个想法..

    让我们变得纯粹

    为了使其更纯粹,我们可以将变量作为参数传递给函数

    const mapWithObject = (obj2, obj1, index) => ({
        itemLabel: obj1.name,
        itemValue: obj1.value - obj2[index].value
    })
    
    // example call
    const result = mapWithObject(arr2, arr1[0], 0)
    

    好的,但现在该函数不再适合 map,因为它需要 3 个参数而不是 2..

    让我们Curry

    const mapWithObject = obj2 => (obj1, index) => ({
      itemLabel: obj1.name,
      itemValue: obj1.value - obj2[index].value
    })
    
    const mapObject_with_arr2 = mapWithObject(arr2)
    
    // example call
    const result = mapObject_with_arr2(arr1[0], 0)
    

    完整代码

    const arr1 = [{
        name: 1,
        value: 10
      },
      {
        name: 2,
        value: 15
      }
    ]
    
    const arr2 = [{
        name: 3,
        value: 5
      },
      {
        name: 4,
        value: 3
      }
    ]
    
    const mapWithObject = obj2 => (obj1, index) => ({
      itemLabel: obj1.name,
      itemValue: obj1.value - obj2[index].value
    })
    
    const mapObject_with_arr2 = mapWithObject(arr2)
    
    const mappedObject = arr1.map(mapObject_with_arr2)
    
    console.log(mappedObject)

    【讨论】:

      【解决方案3】:

      如果您不太关心性能,但想进一步分离您的关注点,您可以使用以下方法:

      • 定义一个在arr1arr2 之间进行“配对”的函数

        [a, b, c] + [1, 2, 3] -> [ [ a, 1 ], [ b, 2 ], [ c, 3 ] ]
        
      • 定义一个函数,清楚地显示两个对象的合并策略

        { a: 1, b: 10 } + { a: 2, b: 20 } -> { a: 1, b: -10 } 
        
      • 定义组成这两个的简单助手,这样您就可以传递原始数组并在一个函数调用中返回所需的输出。

      这是一个例子:

      var arr1=[{name:1,value:10},{name:2,value:15}],arr2=[{name:3,value:5},{name:4,value:3}];
      
      // This is a very general method that bundles two
      // arrays in an array of pairs. Put it in your utils
      // and you can use it everywhere
      const pairs = (arr1, arr2) => Array.from(
        { length: Math.max(arr1.length, arr2.length) },
        (_, i) => [ arr1[i], arr2[i] ]
      );
      
      // This defines our merge strategy for two objects.
      // Ideally, you should give it a better name, based
      // on what the objects represent
      const merge = 
        (base, ext) => ({ 
          itemLabel: base.name,
          itemValue: base.value - ext.value
        });
      
      // This is a helper that "applies" our merge method 
      // to an array of two items.
      const mergePair = ([ base, ext ]) => merge(base, ext);
      
      // Another helper that composes `pairs` and `mergePair` 
      // to allow you to pass your original data.
      const mergeArrays = (arr1, arr2) => pairs(arr1, arr2).map(mergePair);
      
      console.log(mergeArrays(arr1, arr2));

      【讨论】:

        【解决方案4】:

        单人军队

        一个简单的递归程序,在一个函数中处理所有事情。这里有一个明显的混合问题,这会损害函数的整体可读性。我们将在下面看到针对此问题的一种此类补救措施

        const main = ([x, ...xs], [y, ...ys]) =>
          x === undefined || y === undefined
            ? []
            : [ { itemLabel: x.name, itemValue: x.value - y.value } ] .concat (main (xs, ys))
        
        const arr1 =
          [ { name: 1, value: 10 }, { name: 2, value: 15 } ]
        
        const arr2 =
          [ { name: 3, value: 5 }, { name: 4, value: 3 } ]
          
        console.log (main (arr1, arr2))
        // [ { itemLabel: 1, itemValue: 5 },
        //   { itemLabel: 2, itemValue: 12 } ]

        思考类型

        这部分答案受到 Monoid 类别的类型理论的影响——我不会深入探讨它,因为我认为代码应该能够自我展示。

        所以我们的问题中有两种类型:我们称它们为 Foo 和 Bar

        • Foo - 有 namevalue 字段
        • Bar - 有 itemLabelitemValue 字段

        我们可以随心所欲地表示我们的“类型”,但我选择了一个构造对象的简单函数

        const Foo = (name, value) =>
          ({ name
           , value
           })
        
        const Bar = (itemLabel, itemValue) =>
          ({ itemLabel
           , itemValue
           })
        

        创建类型的值

        要构造我们类型的新值,我们只需将函数应用于字段值

        const arr1 =
          [ Foo (1, 10), Foo (2, 15) ]
        
        const arr2 =
          [ Foo (3, 5), Foo (4, 3) ]
        

        让我们看看到目前为止的数据

        console.log (arr1)
        // [ { name: 1, value: 10 },
        //   { name: 2, value: 15 } ]
        
        console.log (arr2)
        // [ { name: 3, value: 5 },
        //   { name: 4, value: 3 } ]
        

        一些高层规划

        我们有了一个良好的开端。我们有两个 Foo 值数组。我们的目标是通过从每个数组中获取一个 Foo 值,将它们组合起来(稍后将详细介绍),然后移动到下一对

        ,来处理这两个数组
        const zip = ([ x, ...xs ], [ y, ...ys ]) =>
          x === undefined || y === undefined
            ? []
            : [ [ x, y ] ] .concat (zip (xs, ys))
        
        console.log (zip (arr1, arr2))
        // [ [ { name: 1, value: 10 },
        //     { name: 3, value: 5 } ],
        //   [ { name: 2, value: 15 },
        //     { name: 4, value: 3 } ] ]
        

        组合值:concat

        通过将 Foo 值正确组合在一起,我们现在可以更专注于组合过程是什么。这里,我要定义一个泛型的concat,然后在我们的Foo类型上实现它

        // generic concat
        const concat = (m1, m2) =>
          m1.concat (m2)
        
        const Foo = (name, value) =>
          ({ name
           , value
           , concat: ({name:_, value:value2}) =>
               // keep the name from the first, subtract value2 from value
               Foo (name, value - value2)
           })
        
        console.log (concat (Foo (1, 10), Foo (3, 5)))
        // { name: 1, value: 5, concat: [Function] }
        

        concat 听起来很熟悉吗? Array 和 String 也是 Monoid 类型!

        concat ([ 1, 2 ], [ 3, 4 ])
        // [ 1, 2, 3, 4 ]
        
        concat ('foo', 'bar')
        // 'foobar'
        

        高阶函数

        所以现在我们有一种方法可以将两个 Foo 值组合在一起。保留第一个 Foo 的 name,并减去 value 属性。现在我们将其应用于“压缩”结果中的每一对。函数式程序员喜欢高阶函数,所以你会欣赏这种高阶和谐

        const apply = f => xs =>
          f (...xs)
        
        zip (arr1, arr2) .map (apply (concat))
        // [ { name: 1, value: 5, concat: [Function] },
        //   { name: 2, value: 12, concat: [Function] } ]
        

        转换类型

        所以现在我们有了正确的 namevalue 值的 Foo 值,但我们希望我们的最终答案是 Bar 值。我们只需要一个专门的构造函数

        Bar.fromFoo = ({ name, value }) =>
          Bar (name, value)
        
        Bar.fromFoo (Foo (1,2))
        // { itemLabel: 1, itemValue: 2 }
        
        zip (arr1, arr2)
          .map (apply (concat))
          .map (Bar.fromFoo)
        // [ { itemLabel: 1, itemValue: 5 },
        //   { itemLabel: 2, itemValue: 12 } ]
        

        努力有回报

        美丽、纯粹的功能性表达。我们的程序读起来非常好;由于采用了声明式风格,数据的流动和转换很容易遵循。

        // main :: ([Foo], [Foo]) -> [Bar]
        const main = (xs, ys) =>
          zip (xs, ys)
            .map (apply (concat))
            .map (Bar.fromFoo)
        

        当然还有完整的代码演示

        const Foo = (name, value) =>
          ({ name
           , value
           , concat: ({name:_, value:value2}) =>
               Foo (name, value - value2)
           })
          
        const Bar = (itemLabel, itemValue) =>
          ({ itemLabel
           , itemValue
           })
        
        Bar.fromFoo = ({ name, value }) =>
          Bar (name, value)
        
        const concat = (m1, m2) =>
          m1.concat (m2)
          
        const apply = f => xs =>
          f (...xs)  
        
        const zip = ([ x, ...xs ], [ y, ...ys ]) =>
          x === undefined || y === undefined
            ? []
            : [ [ x, y ] ] .concat (zip (xs, ys))
        
        const main = (xs, ys) =>
          zip (xs, ys)
            .map (apply (concat))
            .map (Bar.fromFoo)
        
        const arr1 =
          [ Foo (1, 10), Foo (2, 15) ]
          
        const arr2 =
          [ Foo (3, 5), Foo (4, 3) ]
        
        console.log (main (arr1, arr2))
        // [ { itemLabel: 1, itemValue: 5 },
        //   { itemLabel: 2, itemValue: 12 } ]

        备注

        我们上面的程序是用.map-.map 链实现的,这意味着多次处理和创建中间值。我们还在对zip 的调用中创建了一个[[x1,y1], [x2,y2], ...] 的中间数组。范畴论为我们提供了诸如等式推理之类的东西,因此我们可以用m.map(compose(f,g)) 替换m.map(f).map(g) 并获得相同的结果。所以还有改进的空间,但我认为这足以让你咬牙切齿并开始以不同的方式思考问题。

        【讨论】:

        • 阅读这个答案真的很丰富,在某种程度上,它使用与我的答案相同的方法,但更清晰,使用正确的术语并且写得更好;)我了解到我的pairs被称为zip,我的merge本质上是concat,而我的helper that "applies"实际上可以用文字apply函数替换。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-01-19
        • 1970-01-01
        • 2018-01-26
        • 2016-03-08
        • 2018-03-21
        • 2021-01-01
        • 2021-12-28
        相关资源
        最近更新 更多