有什么办法可以“连接”两个JavaScript数组的内容,就像我会在SQL中进行连接一样

我有两个数组:问题和UserProfile

  • userProfiles :[]数组包含{ id, name }对象
  • questions :[]数组包含{ id, text, createdBy }对象

问题中的createdBy整数总是userProfiles中的id值之一。

有没有一种方法,我可以像join两个SQL表一样,“join”数组,就像我使用数据库一样。

我最终需要的是一个包含数组的数组

 { id, text, name } 

这似乎是一个重要的通用问题,虽然有很多答案,但是有一些边界行为,如修改现有数据,解决完全不同的问题,而不是手头的问题,使用多达93,057字节的JavaScript(更不用说了产生错误的结果),产生了过于复杂的额外的数据结构嵌套,每次调用都需要大量的代码,而最严重的是,这个问题的核心并不是解决重要的更通用的问题。

所以,无论好坏,我写了一个shim,用一个方法扩展JavaScript Array对象.joinWith旨在用于by一个公共索引字段this对象数组与对象数组结合使用。 可以在输出中select所需的字段列表(适用于仅需要less数几个字段的情况下合并具有多个字段的对象数组)或者omit输出字段列表(适用于在需要大多数字段时合并对象数组但有一些不是)。

shim代码看起来并不漂亮,所以最后会以OP的特殊types的数据为例来说明如何使用它:

 /* this line will produce the array of objects as desired by the OP */ joined_objects_array = userProfiles.joinWith(questions, 'id', ['createdBy'], 'omit'); /* edit: I just want to make 100% sure that this solution works for you, ie, * does exactly what you need. I haven't seen your actual data, so it's * possible that your IDs are are not in common, (ie, your createdBy * is in common like you said, but not the IDs, and if so you could * morph your data first like this: * questions.map(function(x) { x.id = x.createdBy; }); * before joining the arrays of objects together. * */ 

以下代码用于演示:

 var array1 = [{ id: 3124, name: 'Mr. Smith' }, { id: 710, name: 'Mrs. Jones' }]; var array2 = [{ id: 3124, text: 'wow', createdBy: 'Mr. Jones' }, { id: 710, text: 'amazing' }]; var results_all = array1.joinWith(array2, 'id'); // [{id:3124, name:"Mr. Smith", text:"wow", createdBy:"Mr. Jones"}, // {id:710, name:"Mrs. Jones", text:"amazing"}]* var results_selected = array1.joinWith(array2, 'id', ['id', 'text', 'name']); // [{id:3124, name:"Mr. Smith", text:"wow"}, // {id:710, name:"Mrs. Jones", text:"amazing"}]* /* or equivalently, */ var results_omitted = array1.joinWith(array2, 'id', ['createdBy'], 1); // [{id:3124, name:"Mr. Smith", text:"wow"}, // {id:710, name:"Mrs. Jones", text:"amazing"}]* 

这个解决scheme还有其他一些好处(其中一个保留了通过索引键访问结果数据的能力,尽pipe返回了一个数组)。

请享用!

 /* Array.joinWith - shim by Joseph Myers 7/6/2013 */ if (!Array.prototype.joinWith) { +function () { Array.prototype.joinWith = function(that, by, select, omit) { var together = [], length = 0; if (select) select.map(function(x){select[x] = 1;}); function fields(it) { var f = {}, k; for (k in it) { if (!select) { f[k] = 1; continue; } if (omit ? !select[k] : select[k]) f[k] = 1; } return f; } function add(it) { var pkey = '.'+it[by], pobj = {}; if (!together[pkey]) together[pkey] = pobj, together[length++] = pobj; pobj = together[pkey]; for (var k in fields(it)) pobj[k] = it[k]; } this.map(add); that.map(add); return together; } }(); } 

文档:

  /* this and that both refer to an array of objects, each containing object[by] as one of their fields */ /* NB It is the responsibility of the user of this method to ensure that the contents of the [by] fields are consistent with each other between the two arrays! */ /* select is an array of field names to be included in the resulting objects--all other fields will be excluded, or, if the Boolean value of omit evaluates to true, then select is an array of field names to be excluded from the resulting objects--all others will be included. */ 

我想你想要的是一个内联接 ,它足够简单,可以在JavaScript中实现:

 const innerJoin = (xs, ys, sel) => xs.reduce((zs, x) => ys.reduce((zs, y) => // cartesian product - all combinations zs.concat(sel(x, y) || []), // filter out the rows and columns you want zs), []); 

为了演示的目的,我们将使用以下数据集(谢谢@AshokDamani):

 const userProfiles = [ {id: 1, name: "Ashok"}, {id: 2, name: "Amit"}, {id: 3, name: "Rajeev"}, ]; const questions = [ {id: 1, text: "text1", createdBy: 2}, {id: 2, text: "text2", createdBy: 2}, {id: 3, text: "text3", createdBy: 1}, {id: 4, text: "text4", createdBy: 2}, {id: 5, text: "text5", createdBy: 3}, {id: 6, text: "text6", createdBy: 3}, ]; 

这是你将如何使用它:

 const result = innerJoin(userProfiles, questions, ({id: uid, name}, {id, text, createdBy}) => createdBy === uid && {id, text, name}); 

在SQL方面,这将类似于:

 SELECT questions.id, questions.text, userProfiles.name FROM userProfiles INNER JOIN questions ON questions.createdBy = userProfiles.id; 

把它放在一起:

 const innerJoin = (xs, ys, sel) => xs.reduce((zs, x) => ys.reduce((zs, y) => // cartesian product - all combinations zs.concat(sel(x, y) || []), // filter out the rows and columns you want zs), []); const userProfiles = [ {id: 1, name: "Ashok"}, {id: 2, name: "Amit"}, {id: 3, name: "Rajeev"}, ]; const questions = [ {id: 1, text: "text1", createdBy: 2}, {id: 2, text: "text2", createdBy: 2}, {id: 3, text: "text3", createdBy: 1}, {id: 4, text: "text4", createdBy: 2}, {id: 5, text: "text5", createdBy: 3}, {id: 6, text: "text6", createdBy: 3}, ]; const result = innerJoin(userProfiles, questions, ({id: uid, name}, {id, text, createdBy}) => createdBy === uid && {id, text, name}); console.log(result); 

我只是经常使用underscore.js,因为它对数组和“map reduce”有很好的支持,可以解决这个问题。

这里是一个解决你的问题的小提琴(它假定每个用户只有一个问题,如你原来的post所示)

http://jsfiddle.net/x5Z7f/

(打开浏览器控制台查看输出)

  var userProfiles = [{ id:'1', name:'john' }, { id:'2', name:'mary' }]; var questions =[ { id:'1', text:'question john', createdBy:'1' }, { id:'2', text:'question mary', createdBy:'2' }]; var rows = _.map(userProfiles, function(user){ var question = _.find(questions, function(q){ return q.createdBy == user.id }); user.text = question? question.text:''; return user; }) _.each(rows, function(row){ console.log(row) }); 

上面的答案假设您使用id == createdBy作为join列。

如果是我,我会按以下方式处理:

设置:

 var userProfiles = [], questions = []; userProfiles.push( {id:1, name:'test'} ); userProfiles.push( {id:2, name:'abc'} ); userProfiles.push( {id:3, name:'def'} ); userProfiles.push( {id:4, name:'ghi'} ); questions.push( {id:1, text:'monkey', createdBy:1} ); questions.push( {id:2, text:'Monkey', createdBy:1} ); questions.push( {id:3, text:'big', createdBy:2} ); questions.push( {id:4, text:'string', createdBy:2} ); questions.push( {id:5, text:'monKey', createdBy:3} ); 

首先,创build一个查找对象,其中链接ID被用作关键字

 var createObjectLookup = function( arr, key ){ var i, l, obj, ret = {}; for ( i=0, l=arr.length; i<l; i++ ) { obj = arr[i]; ret[obj[key]] = obj; } return ret; }; var up = createObjectLookup(userProfiles, 'id'); 

现在你已经拥有了这个function,应该很容易地解决这些问题,并find合并的用户对象:

 var i, l, question, user, result = []; for ( i=0, l=questions.length; i<l; i++ ) { if ( (question = questions[i]) && (user = up[question.createdBy]) ) { result.push({ id: question.id, text: question.text, name: user.name }); } } 

你现在应该有你需要的一切result

 console.log(result); 

所有你想要的是ResultArray计算如下:

  var userProfiles1= new Array(1, "ashok"); var userProfiles2= new Array(2, "amit"); var userProfiles3= new Array(3, "rajeev"); var UArray = new Array(userProfiles1, userProfiles2, userProfiles3); var questions1= new Array(1, "text1", 2); var questions2= new Array(2, "text2", 2); var questions3= new Array(3, "text3", 1); var questions4= new Array(4, "text4", 2); var questions5= new Array(5, "text5", 3); var questions6= new Array(6, "text6", 3); var QArray = new Array(questions1, questions2, questions3, questions4, questions5, questions6); var ResultArray = new Array(); for (var i=0; i<UArray.length; i++) { var uid = UArray[i][0]; var name = UArray[i][1]; for(var j=0; j<QArray.length; j++) { if(uid == QArray[j][2]) { var qid = QArray[j][0] var text = QArray[j][1]; ResultArray.push(qid +"," + text +","+ name) } } } for(var i=0; i<ResultArray.length; i++) { document.write(ResultArray[i] + "<br>") } 

演示: http : //jsfiddle.net/VqmVv/

这是我试图做出一个通用的解决scheme。 我在这里使用Array.mapArray.index方法:

 var arr1 = [ {id: 1, text:"hello", oid:2}, {id: 2, text:"juhu", oid:3}, {id: 3, text:"wohoo", oid:4}, {id: 4, text:"yeehaw", oid:1} ]; var arr2 = [ {id: 1, name:"yoda"}, {id: 2, name:"herbert"}, {id: 3, name:"john"}, {id: 4, name:"walter"}, {id: 5, name:"clint"} ]; function merge(arr1, arr2, prop1, prop2) { return arr1.map(function(item){ var p = item[prop1]; el = arr2.filter(function(item) { return item[prop2] === p; }); if (el.length === 0) { return null; } var res = {}; for (var i in item) { if (i !== prop1) { res[i] = item[i]; } } for (var i in el[0]) { if (i !== prop2) { res[i] = el[0][i]; } } return res; }).filter(function(el){ return el !== null; }); } var res = merge(arr1, arr2, "oid", "id"); console.log(res); 

所以基本上你可以为每个数组定义两个数组和一个属性,这样prop1将被replace为数组2中属性prop2等于prop1的所有属性。

在这种情况下的结果是:

 var res = [ {id: 1, text:"hello", name:"herbert"}, {id: 2, text:"juhu", name:"john"}, {id: 3, text:"wohoo", name:"walter"}, {id: 4, text:"yeehaw", name:"yoda"} ]; 

请注意,如果有多个匹配项,则将使用第一个项目,如果不匹配,则将从结果数组中删除该对象。

小提琴

只是想分享一些通用的代码:

 // Create a cartesian product of the arguments. // product([1,2],['a','b'],['X']) => [[1,"a","X"],[1,"b","X"],[2,"a","X"],[2,"b","X"]] // Accepts any number of arguments. product = function() { if(!arguments.length) return [[]]; var p = product.apply(null, [].slice.call(arguments, 1)); return arguments[0].reduce(function(r, x) { return p.reduce(function(r, y) { return r.concat([[x].concat(y)]); }, r); }, []); } 

你的问题:

 result = product(userProfiles, questions).filter(function(row) { return row[0].id == row[1].createdBy; }).map(function(row) { return { userName: row[0].name, question: row[1].text } }) 

我不知道任何允许这样做的内置函数。

你可以编写自己的函数,类似于这个jsFiddle :

 var userProfiles = [{id:1, name:'name1'},{id:2,name:'name2'}]; var questions = [ {id:1, text:'text1', createdBy:'foo'}, {id:1, text:'text2', createdBy:'bar'}, {id:2, text:'text3', createdBy:'foo'}]; merged = mergeMyArrays(userProfiles,questions); console.log(merged); /** * This will give you an array like this: * [{id:1, name:name1, text:text1}, {...] * params : 2 arrays to merge by id */ function mergeMyArrays(u,q){ var ret = []; for(var i = 0, l = u.length; i < l; i++){ var curU = u[i]; for(var j = 0, m = q.length; j<m; j++){ if(q[j].id == curU.id){ ret.push({ id: curU.id, name: curU.name, text: q[j].text }); } } } return ret; } 

或者如果你想要一个更好的“join”(SQL-Y):

 var userProfiles = [{id:1, name:'name1'},{id:2,name:'name2'}]; var questions = [ {id:1, text:'text1', createdBy:'foo'}, {id:1, text:'text2', createdBy:'bar'}, {id:2, text:'text3', createdBy:'foo'}]; merged = mergeMyArrays(userProfiles,questions); console.log(merged); /** * This will give you an array like this: * [{id:1, name:name1, questions:[{...}]] * params : 2 arrays to merge by id */ function mergeMyArrays(u,q){ var ret = []; for(var i = 0, l = u.length; i < l; i++){ var curU = u[i], curId = curU.id, tmpObj = {id:curId, name:curU.name, questions:[]}; for(var j = 0, m = q.length; j<m; j++){ if(q[j].id == curId){ tmpObj.questions.push({ text: q[j].text, createdBy: q[j].createdBy }); } } ret.push(tmpObj); } return ret; } 

就像在这个jsFiddle

你可以使用reduce和map来做到这一点。

首先,创build一个从ID到名称的映射:

 var id2name = userProfiles.reduce(function(id2name, profile){ id2name[profile.id] = profile.name; return id2name; }, {}); 

其次,创build一个新的问题arrays,但创build问题的用户的名称代替他们的ID:

 var qs = questions.map(function(q){ q.createdByName = id2name[q.createdBy]; delete q.createdBy; return q; }); 

这很容易与StrelkiJS完成

 var userProfiles = new StrelkiJS.IndexedArray(); userProfiles.loadArray([ {id: 1, name: "Ashok"}, {id: 2, name: "Amit"}, {id: 3, name: "Rajeev"} ]); var questions = new StrelkiJS.IndexedArray(); questions.loadArray([ {id: 1, text: "text1", createdBy: 2}, {id: 2, text: "text2", createdBy: 2}, {id: 3, text: "text3", createdBy: 1}, {id: 4, text: "text4", createdBy: 2}, {id: 5, text: "text5", createdBy: 3}, {id: 6, text: "text6", createdBy: 3} ]); var res=questions.query([{ from_col: "createdBy", to_table: userProfiles, to_col: "id", type: "outer" }]); 

结果将是:

 [ [ {"id":1,"text":"text1","createdBy":2}, {"id":2,"name":"Amit"} ], [ {"id":2,"text":"text2","createdBy":2}, {"id":2,"name":"Amit"} ], [ {"id":3,"text":"text3","createdBy":1}, {"id":1,"name":"Ashok"} ], [ {"id":4,"text":"text4","createdBy":2}, {"id":2,"name":"Amit"} ], [ {"id":5,"text":"text5","createdBy":3}, {"id":3,"name":"Rajeev"} ], [ {"id":6,"text":"text6","createdBy":3}, {"id":3,"name":"Rajeev"} ] ] 

您可以先使用jQuery.merge(),然后使用jQuery.unique()来实现此目的。 merge()会将所有项目添加到一个数组中,而unique()将从该数组中删除重复项。

http://api.jquery.com/jQuery.merge/

http://api.jquery.com/jQuery.unique/