map使用相同的索引添加/减少两个数组对象

时间:2018-01-05 09:01:32

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

我有两个数组对象如下:

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

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

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

输出:

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

我现在正在做以下事情:

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

有没有更好的解决方案呢?

4 个答案:

答案 0 :(得分: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:这些都没有比你已经做过的“更好”。

答案 1 :(得分:1)

一人军

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



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] } ]

转换类型

现在我们的Foo值具有正确的namevalue值,但我们希望我们的最终答案为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链实现的,这意味着多次处理和创建中间值。我们在调用[[x1,y1], [x2,y2], ...]时创建了一个zip的中间数组。类别理论为我们提供了诸如等式推理之类的东西,因此我们可以用m.map(f).map(g)替换m.map(compose(f,g))并获得相同的结果。所以还有改进空间的空间,但我认为这足以削减你的牙齿并开始以不同的方式思考问题。

答案 2 :(得分:0)

为了使您的解决方案更具功能性,您需要将匿名函数更改为纯(匿名)函数。

  

纯函数是一个函数,在给定相同输入的情况下,它将始终返回相同的输出

匿名函数取决于可变变量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 :(得分:0)

如果您不太关心性能,但想要进一步区分您的疑虑,可以使用这种方法:

  • 定义一个执行&#34;配对的功能&#34;在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 } 
    
  • 定义构成两者的简单助手,这样您就可以传递原始数组,并在一次函数调用中返回所需的输出。

以下是一个例子:

&#13;
&#13;
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));
&#13;
&#13;
&#13;