Combine 2 lists into one using map & arrow function

依然范特西╮ 提交于 2020-02-25 02:09:07

问题


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:


  1. 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 for null values in desired output. So, I'd propose a solution different from the one of Christos Lytras.
    Iterating over fields in each row iteration will give you O(N^M) complexity. Where N - is rows.length, and M is fields.length. This is possibly a bad idea. The following code will give you linear complexity O(N+M). Where M is still fields.length and N is a sum of numbers of fields in each row of rows, it sounds even scarier than O(N^M) but, if you have optional fields it will save you a fortune - look for called 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],
            }))
        )
    }
    
  2. 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 as map 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 (press Run 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!