简体   繁体   English

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

[英]map add/reduce two array object with same index

I have two array object as following: 我有两个数组对象如下:

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

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

I want to redefine the key and reduce each data with the same index. 我想重新定义密钥并使用相同的索引减少每个数据。

output: 输出:

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

I'm doing now as following: 我现在正在做如下:

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

Is there any better solution of doing this? 这样做有什么更好的解决方案吗?

Your code is just fine, you could use recursion as well: 你的代码很好,你也可以使用递归:

 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)); 

Both functions implementing a map or reduce would have to use either arr1 or arr2 outside of their scope (not passed to it as parameter) so strictly speaking not pure. 实现mapreduce两个函数都必须在其作用域之外使用arr1或arr2(不作为参数传递给它),因此严格来说不是纯粹的。 But you could easily solve it with partial application: 但你可以通过部分应用轻松解决它:

 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)); 

But as CodingIntrigue mentioned in the comment: none of these are any "better" than you've already done. 但正如CodingIntrigue在评论中提到的那样:这些都不比你已经做过的“更好”。

One-man army 孤胆英雄

A simple recursive program that handles everything in a single function. 一个简单的递归程序,可以处理单个函数中的所有内容。 There's a clear mixture of concerns here which hurts of function's overall readability. 这里有一个明显的混合问题,这会损害功能的整体可读性。 We'll see one such remedy for this problem below 我们将在下面看到针对此问题的一种此类补救措施

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


Thinking with types 用类型思考

This part of the answer is influenced by type theory from the Monoid category – I won't go too far into it because I think the code should be able to demonstrate itself. 答案的这一部分受到Monoid类别的类型理论的影响 - 我不会对它进行太深入的研究,因为我认为代码应该能够证明自己。

So we have two types in our problem: We'll call them Foo and Bar 所以我们在问题中有两种类型:我们称它们为Foo和Bar

  • Foo – has name , and value fields Foo - 有namevalue字段
  • Bar – has itemLabel and itemValue fields Bar - 有itemLabelitemValue字段

We can represent our "types" however we want, but I chose a simple function which constructs an object 我们可以代表我们想要的“类型”,但我选择了一个构造对象的简单函数

const Foo = (name, value) =>
  ({ name
   , value
   })

const Bar = (itemLabel, itemValue) =>
  ({ itemLabel
   , itemValue
   })

Making values of a type 制作一种类型的价值观

To construct new values of our type, we just apply our functions to the field values 要构造我们类型的新值,我们只需将函数应用于字段值

const arr1 =
  [ Foo (1, 10), Foo (2, 15) ]

const arr2 =
  [ Foo (3, 5), Foo (4, 3) ]

Let's see the data we have so far 让我们看看目前为止的数据

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

console.log (arr2)
// [ { name: 3, value: 5 },
//   { name: 4, value: 3 } ]

Some high-level planning 一些高层次的规划

We're off to a great start. 我们有一个良好的开端。 We have two arrays of Foo values. 我们有两个Foo值数组。 Our objective is to work through the two arrays by taking one Foo value from each array, combining them (more on this later), and then moving onto the next pair 我们的目标是通过从每个数组中取一个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 } ] ]

Combining values: concat 结合价值: concat

With the Foo values properly grouped together, we can now focus more on what that combining process is. 通过将Foo值正确地组合在一起,我们现在可以更多地关注组合过程。 Here, I'm going to define a generic concat and then implement it on our Foo type 在这里,我将定义一个通用的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] }

Does concat sound familiar? concat听起来很熟悉吗? Array and String are also Monoid types! 数组和字符串也是Monoid类型!

concat ([ 1, 2 ], [ 3, 4 ])
// [ 1, 2, 3, 4 ]

concat ('foo', 'bar')
// 'foobar'

Higher-order functions 高阶函数

So now we have a way to combine two Foo values together. 所以现在我们有办法将两个Foo值组合在一起。 The name of the first Foo is kept, and the value properties are subtracted. 保留第一个Foo的name ,并减去value属性。 Now we apply this to each pair in our "zipped" result. 现在我们在“压缩”结果中将它应用于每一对。 Functional programmers love higher-order functions, so you'll appreciate this higher-order harmony 功能程序员喜欢高阶函数,所以你会欣赏这种高阶和谐

const apply = f => xs =>
  f (...xs)

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

Transforming types 转换类型

So now we have the Foo values with the correct name and value values, but we want our final answer to be Bar values. 所以现在我们有了具有正确namevalue的Foo值,但我们希望我们的最终答案是Bar值。 A specialized constructor is all we need 我们需要一个专门的构造函数

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

Hard work pays off 努力工作会有成果

A beautiful, pure functional expression. 一个美丽,纯粹的功能表达。 Our program reads very nicely; 我们的课程阅读得非常好; flow and transformation of the data is easy to follow thanks to the declarative style. 由于声明式风格,数据的流动和转换很容易遵循。

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

And a complete code demo, of course 当然还有完整的代码演示

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


Remarks 备注

Our program above is implemented with a .map - .map chain which means handling and creating intermediate values multiple times. 我们上面的程序是用.map - .map链实现的,这意味着多次处理和创建中间值。 We also created an intermediate array of [[x1,y1], [x2,y2], ...] in our call to zip . 我们还在zip调用中创建了[[x1,y1], [x2,y2], ...]的中间数组。 Category theory gives us things like equational reasoning so we could replace m.map(f).map(g) with m.map(compose(f,g)) and achieve the same result. 类别理论为我们提供了诸如等式推理之类的东西,因此我们可以用m.map(compose(f,g))替换m.map(f).map(g) m.map(compose(f,g))并获得相同的结果。 So there's room to improve this yet, but I think this is just enough to cut your teeth and start thinking about things in a different way. 所以还有改进空间的空间,但我认为这足以削减你的牙齿并开始以不同的方式思考问题。

To make your solution more functional you need to change your anonymous function to a pure (anonymous) function. 为了使您的解决方案更具功能性,您需要将匿名函数更改为纯(匿名)函数。

A pure function is a function that, given the same input, will always return the same output 纯函数是一个函数,在给定相同输入的情况下,它将始终返回相同的输出

The anonymous function depends on the mutable variable arr1 and arr2 . 匿名函数取决于可变变量arr1arr2 That means that it depends on the system state. 这意味着它取决于系统状态。 So it doesn't fit into the pure function rule. 所以它不适合纯函数规则。

The following is maybe not the best implementaion but I hope it gives you an idea.. 以下可能不是最好的实现,但我希望它能给你一个想法..

Let's Make it Pure 让它变得纯净

To make it pure we can pass the variables into the function as arguments 为了使它纯粹,我们可以将变量作为参数传递给函数

const mapWithObject = (obj2, obj1, index) => ({
    itemLabel: obj1.name,
    itemValue: obj1.value - obj2[index].value
})

// example call
const result = mapWithObject(arr2, arr1[0], 0)

Ok, but now the function doesn't fit into map anymore because it takes 3 arguments instead of 2.. 好的,但现在该功能不再适合map了,因为它需要3个参数而不是2个...

Let's Curry it 我们来吧

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)

Full Code 完整代码

 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) 

If you don't care to much about performance, but want to separate your concerns a bit further you could use this approach: 如果您不太关心性能,但想要进一步区分您的关注点,您可以使用此方法:

  • Define a function that does the "pairing" between arr1 and arr2 定义一个在arr1arr2之间进行“配对”的函数

     [a, b, c] + [1, 2, 3] -> [ [ a, 1 ], [ b, 2 ], [ c, 3 ] ] 
  • Define a function that clearly shows the merge strategy of two objects 定义一个清楚地显示两个对象的合并策略的函数

     { a: 1, b: 10 } + { a: 2, b: 20 } -> { a: 1, b: -10 } 
  • Define simple helpers that compose the two so you can pass your original arrays and be returned the desired output in one function call. 定义组成两者的简单助手,这样您就可以传递原始数组,并在一次函数调用中返回所需的输出。

Here's an example: 这是一个例子:

 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)); 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM