Tengo dos matrices: Question y UserProfile
- La matriz : [] contiene objetos
userProfiles
{ id, name }
- La matriz : [] contiene objetos
questions
{ id, text, createdBy }
El entero en las preguntas es siempre uno de los valores de id en .createdBy
userProfiles
¿Hay alguna manera de «unir» las matrices de la misma manera que uniría dos tablas SQL si estuviera usando una base de datos?
Lo que necesito como resultado final es una matriz que contenga
{ id, text, name }
El SQL correspondiente sería:
SELECT u.id, q.text, u.name
FROM userProfiles u
JOIN questions q ON q.createdBy=u.id
Solución
Creo que lo que quieres es una unión interna, que sea lo suficientemente simple como para implementarla en 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), []);
Para fines de demostración, utilizaremos el siguiente conjunto de datos (gracias @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},
];
Así es como lo usarías:
const result = innerJoin(userProfiles, questions,
({id: uid, name}, {id, text, createdBy}) =>
createdBy === uid && {id, text, name});
En términos SQL esto sería similar a:
SELECT questions.id, questions.text, userProfiles.name
FROM userProfiles INNER JOIN questions
ON questions.createdBy = userProfiles.id;
Poniéndolo todo junto:
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("Open your browser console to see the output.");
console.table(result);
Editar: Sin embargo, esta no es la mejor solución. Dado que la solución anterior se repite a través del producto cartesiano, lleva tiempo ejecutarla. Con un poco de modificación podemos hacer que se ejecute a tiempo, @pebbl encontrado primero:O(m × n)
O(m + n)
const equijoin = (xs, ys, primary, foreign, sel) => {
const ix = xs.reduce((ix, row) => // loop through m items
ix.set(row[primary], row), // populate index for primary table
new Map); // create an index for primary table
return ys.map(row => // loop through n items
sel(ix.get(row[foreign]), // get corresponding row from primary
row)); // select only the columns you need
};
Ahora puede usarlo de la siguiente manera:
const result = equijoin(userProfiles, questions, "id", "createdBy",
({name}, {id, text}) => ({id, text, name}));
Poniéndolo todo junto:
const equijoin = (xs, ys, primary, foreign, sel) => {
const ix = xs.reduce((ix, row) => ix.set(row[primary], row), new Map);
return ys.map(row => sel(ix.get(row[foreign]), row));
};
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 = equijoin(userProfiles, questions, "id", "createdBy",
({name}, {id, text}) => ({id, text, name}));
console.log("Open your browser console to see the output.");
console.table(result);
Otras respuestas
i just about always use underscore.js because it has such good support for arrays and «map reduce» which this problem can be solved with.
here is a fiddle with a solution for your question ( it assumes there is only one question per user as your original post suggests)
(open the browser console to see the output)
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) });
the above answer assumes you are using id == createdBy as the joining column.