简体   繁体   中英

Create array of objects using distinct values from two arrays with javascript

My question is a little more involved. I have two arrays of data:

var arr = [
  {title: "foo", namesIndices: [0,1]},
  {title: "bar", namesIndices: [2]},
  {title: "baz", namesIndices: [3,4]}
];

var names = [
  {name: "John", website: "someUrl"},
  {name: "Mike", website: "someUrl"},
  {name: "Jane", website: "someUrl"},
  {name: "Ali", website: "someUrl"},
  {name: "Robert", website: "someUrl"}
];

Where namesIndices will reference an index in the names array that will correspond with that person with their title. In order to match the person's name and website with the correct title to make this array:

var person = [
  {name: "John", title: "foo", website: "someUrl"},
  {name: "Mike", title: "foo", website: "someUrl"},
  {name: "Jane", title: "bar", website: "someUrl"},
  {name: "Ali", title: "baz", website: "someUrl"},
  {name: "Robert", title: "baz", website: "someUrl"}
];

I have had to loop through the first array, then loop through arr.namesIndices:

var person = [];
for (var i = 0; i < arr.length; i++) {
    for (var j = 0; j < arr[i].namesIndices.length; j++) {
        var personObj = {};
        personObj.title= arr[i].title;
        personObj.name = names[arr[i].namesIndices[j]].name;
        personObj.website= names[arr[i].namesIndices[j]].website;
        person.push(personObj);
     }
 }

Is there a way to do this without nested loops?

You can use reduce() and map() to avoid explicit loops altogether if you wish.

var arr = [
  {title: "foo", namesIndices: [0,1]},
  {title: "bar", namesIndices: [2]},
  {title: "baz", namesIndices: [3,4]}
];

var names = [
  {name: "John", website: "someUrl"},
  {name: "Mike", website: "someUrl"},
  {name: "Jane", website: "someUrl"},
  {name: "Ali", website: "someUrl"},
  {name: "Robert", website: "someUrl"}
];

var person = arr.reduce(function (accumulator, titleAndIndices, arrIndex) {
    var newEntries = titleAndIndices.namesIndices.map(function (index) {
        var rv = names[index];
        rv.title = arr[arrIndex].title;
        return rv;
    });
    return accumulator.concat(newEntries);
}, []);

 console.log(person);

Still requires two loops, but they're not nested now:

var arr = [
  {title: "foo", namesIndices: [0, 1]},
  {title: "bar", namesIndices: [2]},
  {title: "baz", namesIndices: [3, 4, 5]}
];

var names = [
  {name: "John", website: "someUrl"},
  {name: "Mike", website: "someUrl"},
  {name: "Jane", website: "someUrl"},
  {name: "Ali", website: "someUrl"},
  {name: "Robert", website: "someUrl"}
];

var people = [];

var makePeople = function(title, indices){
    for(var i = 0; i < indices.length; i++){
        people.push({
            name: names[indices[i]].name,
            title: title,
            website: names[indices[i]].website
        });
    }
}

for(var i = 0; i < arr.length; i++){
    makePeople(arr[i].title, arr[i].nameIndices);
}

It's not exactly shorter though, and it does basically the same thing.

Is there a way to do this without nested loops?

No. And there is nothing wrong with it. Your data comes in a nested format, so it is totally natural to consume it with nested control structures. That still has O(n) complexity, where n is the amount of data (and n = al * ni with the arr length and the average number of namesIndices per item)

If you're willing to destroy your arr array and repurpose your names array (as the accepted answer does), you can reach your goal via a simple recursive function. I'm considering this to be loopless since it doesn't use loop syntax. I think that's about as close as you can get to looplessness, short of hardcoding a separate process for every array index.

Recursive function

var person = (function func(obj) {
  names[obj.namesIndices.pop()].title = obj.title;
  return obj.namesIndices.length ? func(obj) : arr.length ? func(arr.pop()) : names;
})(arr.pop());

You could preserve the original arrays if you made the function keep track of the array indexes instead of using pop(), but I didn't have the patience to do it that way :).

If you can tolerate a single loop, then you can replace the nested one with conditional statements that determine which counter to increment. Unlike the previous example, this one doesn't alter the original arrays.

Single loop

var person = [];

for(var a = arr.length - 1, n = arr[a].namesIndices.length - 1, obj, i;;) {
  i = arr[a].namesIndices[n];
  obj = names[i];
  person[i] = {name: obj.name, website: obj.website, title: arr[a].title};
  if(n) n--;
  else if(a) n = arr[--a].namesIndices.length - 1;
  else break;
}

I haven't properly tested the performance of these two techniques, but I believe it will be at least comparable to that of the nested loop, and better than any solution that involves Array iteration methods like reduce , map , forEach , etc.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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