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?
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.
So we have two types in our problem: We'll call them Foo and Bar
Foo
– has name
, and value
fieldsBar
– has itemLabel
and itemValue
fieldsWe 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. 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
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
With the Foo values properly grouped together, we can now focus more on what that combining process is. Here, I'm going to define a generic concat
and then implement it on our Foo type
// 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? Array and String are also Monoid types!
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. The name
of the first Foo is kept, and the value
properties are subtracted. 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. 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. We also created an intermediate array of [[x1,y1], [x2,y2], ...]
in our call to zip
. 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. 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.
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. 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With