问题
I have 2 lists and I want to combine them so that I can populate them into a list. I know this can be done using nested for loops but, I'm trying to avoid for loops because of the amount of data I'll have to loop on. I would like to achieve this using the arrow functions or anything else.
List One:
let fields = [
{
field: "Name",
fieldType: "Text"
},
{
field: "Active__c",
fieldType: "Boolean"
},
{
field: "Contact",
fieldType: "Relationship"
}
];
List Two:
let rows = [
{
contact: {
Name: "Joe",
Active__c: true,
Contact: "SomeContact"
}
},
{
contact: {
Name: "Rachel",
Active__c: true
}
},
{
contact: {
Name: "Ross",
Active__c: true
}
},
{
contact: {
Name: "Monica",
Active__c: true
}
}
];
Current code:
let output = rows.map(row => ({
id: row.Id,
data: {
value: fields.map(field => (row.contact[field.field])),
field: fields.map(field => field.field)
}
}));
The output of this code:
[
{
"data": {
"value": [
"Joe",
true,
"SomeContact"
],
"field": [
"Name",
"Active__c",
"Contact"
]
}
},
{
"data": {
"value": [
"Rachel",
true,
null
],
"field": [
"Name",
"Active__c",
"Contact"
]
}
},
{
"data": {
"value": [
"Ross",
true,
null
],
"field": [
"Name",
"Active__c",
"Contact"
]
}
},
{
"data": {
"value": [
"Monica",
true,
null
],
"field": [
"Name",
"Active__c",
"Contact"
]
}
}
]
Desired output:
[
data : [
[
{
field : "Name",
type: "Text",
value : "Joe"
},
{
field : "Active__c",
type: "Boolean",
value : true
},
{
field : "Contact",
type: "Relationship",
value : "SomeContact"
}
],
[
{
field : "Name",
type: "Text",
value : "Rachel"
},
{
field : "Active__c",
type: "Boolean",
value : false
},
{
field : "Contact",
type: "Relationship",
value : "SomeContact Two"
}
],
[
...
],
[
...
]
]
]
How can I achieve this?
回答1:
The data
property is unique and it has to be defined inline creating an object (not an array as you have in your desired output). You have to map fields
array to each element of rows
and then fill each field
data with the row
data if they exist. Also, I can't see an Id
field on any row object inside rows
array. This code sets null
if a field
does not exists:
let output = {
data: rows.map(({ contact }) =>
fields.map(({ field, fieldType: type }) => ({
field,
type,
value: field in contact ? contact[field] : null // Set null if contact has no field
}))
)
}
Run this code snippet to see the results:
let fields = [
{
field: "Name",
fieldType: "Text"
},
{
field: "Active__c",
fieldType: "Boolean"
},
{
field: "Contact",
fieldType: "Relationship"
}
];
let rows = [
{
contact: {
Name: "Joe",
Active__c: true,
Contact: "SomeContact"
}
},
{
contact: {
Name: "Rachel",
Active__c: true
}
},
{
contact: {
Name: "Ross",
Active__c: true
}
},
{
contact: {
Name: "Monica",
Active__c: true
}
}
];
let output = {
data: rows.map(({ contact }) =>
fields.map(({ field, fieldType: type }) => ({
field,
type,
value: field in contact ? contact[field] : null
}))
)
}
document.getElementById('output').appendChild(
document.createTextNode(JSON.stringify(output, null, 2))
);
<pre id="output"></pre>
回答2:
It's not the loops you should worry about, but algorithm's complexity. As I see, you have optional fields in
rows
, and you didn't ask fornull
values in desired output. So, I'd propose a solution different from the one of Christos Lytras.
Iterating overfields
in each row iteration will give youO(N^M)
complexity. WhereN
- isrows.length
, andM
isfields.length
. This is possibly a bad idea. The following code will give you linear complexityO(N+M)
. WhereM
is stillfields.length
andN
is a sum of numbers of fields in each row ofrows
, it sounds even scarier thanO(N^M)
but, if you have optional fields it will save you a fortune - look forcalled X times
in snippet output.// prepare dictionary, later on fields_dict[field] will have O(1) complexity const fields_dict = fields.reduce((acc, {field, fieldType}) => { acc[field] = fieldType return acc }, {}) let output2 = { data: rows.map(({ contact }) => Object.keys(contact).map(field => ({ // iterate only over existing fields field, type: fields_dict[field], value: contact[field], })) ) }
And by the way
I know this can be done using nested for loops but, I'm trying to avoid for loops because of the amount of data I'll have to loop on function
...even in modern browsers loops are superior in performance to
map()
,reduce()
and Co., not the other way around.Look at the timings in the snippet. At least in my environment
for
version is twice as fast asmap
version (on the first run). Of cause, at that moment code is in no way hot by the standards of JIT-compiler, so code haven't been optimized by the browser. After JIT-compilation difference in performance become negligible (pressRun code snippet
for a couple of times to see). Still, loops are faster, at least on the first run.But if you won't test performance of you code, then don't bother microoptimizing it. Better think of algorithm complexity. And, yes, use functional style - it's both easier to write and to read.
let fields = [ { field: "Name" , fieldType: "Text" }, { field: "Active__c", fieldType: "Boolean" }, { field: "Contact" , fieldType: "Relationship" }, { field: "extra1" , fieldType: "something" }, { field: "extra2" , fieldType: "something" }, { field: "extra3" , fieldType: "something" }, { field: "extra4" , fieldType: "something" }, ]; let rows = [ { contact: { Name: "Joe" , Active__c: true, Contact: "SomeContact" } }, { contact: { Name: "Rachel", Active__c: true } }, { contact: { Name: "Ross" , Active__c: true } }, { contact: { Name: "Monica", Active__c: true } }, { contact: { Name: "Monica", Active__c: true } }, { contact: { Name: "Monica", Active__c: true } }, { contact: { Name: "Monica", Active__c: true } }, { contact: { Name: "Monica", Active__c: true } }, { contact: { Name: "Monica", Active__c: true } }, { contact: { Name: "Monica", Active__c: true } }, ]; let i i = 0 console.time("Christos Lytras version") let output1 = { data: rows.map(({ contact }) => fields.map(({ field, fieldType: type }) => (i++,{ field, type, value: field in contact ? contact[field] : null })) ) } console.timeEnd("Christos Lytras version") console.log(`called ${i} times`) i = 0 let j = 0 console.time("functional version") const fields_dict = fields.reduce((acc, {field, fieldType}) => { i++, acc[field] = fieldType; return acc }, {}) let output2 = { data: rows.map(({ contact }) => Object.keys(contact).map(field => (j++,{ field, type: fields_dict[field], value: contact[field], }))) } console.timeEnd("functional version") console.log(`called ${i+j} times`) i = 0 console.time("loop version") const fields_dict2 = {} for(const {field, fieldType} of fields) { i++; fields_dict2[field] = fieldType } const output3 = { data: new Array(rows.length) } j = 0 for(let r = 0 ; r !== rows.length ; ++r ) { const contact = rows[r].contact const contact_another_format = output3.data[r] = [] for(const field in contact) { j++ contact_another_format.push({ field, type: fields_dict2[field], value: contact[field], }) } } console.timeEnd("loop version") console.log(`called ${i+j} times`) // console.log(JSON.stringify(output1, undefined, 2)) // console.log(JSON.stringify(output2, undefined, 2)) console.log(JSON.stringify(output3, undefined, 2)) console.log("results are equal:", JSON.stringify(output2) === JSON.stringify(output3)) // intentionally not equal to output1
来源:https://stackoverflow.com/questions/60222532/combine-2-lists-into-one-using-map-arrow-function