56 votes

Est-il possible de "joindre" le contenu de deux tableaux javascript de la même manière que je le ferais avec SQL?

J'ai deux tableaux: Question et UserProfile

  • L' userProfiles: tableau [] contiennent { id, name } objets
  • L' questions: [] tableau contient { id, text, createdBy } objets

L' createdBy entier en question est toujours l'une des valeurs d'id en userProfiles.

Est-il une manière que je pourrais "rejoindre" la tableaux de la même façon que je l'aurais jointure de deux tables SQL si j'ai été en utilisant une base de données.

Ce dont j'ai besoin comme un résultat de fin est un tableau qui contient

{ id, text, name }

58voto

Aadit M Shah Points 17951

Je pense que ce que vous voulez est une jointure interne, qui est assez simple à mettre en œuvre en JavaScript:

function innerJoin(a, b, select) {
    var m = a.length, n = b.length, c = [];

    for (var i = 0; i < m; i++) {
        var x = a[i];

        for (var j = 0; j < n; j++) { // cartesian product - all combinations
            var y = select(x, b[j]);  // filter out the rows and columns you want
            if (y) c.push(y);         // if a row is returned add it to the table
        }
    }

    return c;
}

Pour les besoins de la démonstration, nous allons utiliser les données suivantes (merci @AshokDamani):

var userProfiles = [
    {id: 1, name: "Ashok"},
    {id: 2, name: "Amit"},
    {id: 3, name: "Rajeev"}
];

var 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}
];

C'est comment vous pouvez l'utiliser:

var c = innerJoin(userProfiles, questions, function (userProfile, question) {
    if (question.createdBy === userProfile.id) {
        return {
            id: question.id,
            text: question.text,
            name: userProfile.name
        };
    }
});

Dans SQL, cela serait semblable à:

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

Voici le travail de démonstration: http://jsfiddle.net/zvHVv/


Edit: mais ce n'est pas la meilleure solution. Depuis la solution ci-dessus boucles à travers le produit Cartésien il prend O(m × n) du temps. Avec un peu de modification, nous pouvons le faire fonctionner en O(m + n) du temps - @pebbl trouvé en premier:

function equijoin(primary, foreign, primaryKey, foreignKey, select) {
    var m = primary.length, n = foreign.length, index = [], c = [];

    for (var i = 0; i < m; i++) {     // loop through m items
        var row = primary[i];
        index[row[primaryKey]] = row; // create an index for primary table
    }

    for (var j = 0; j < n; j++) {     // loop through n items
        var y = foreign[j];
        var x = index[y[foreignKey]]; // get corresponding row from primary
        c.push(select(x, y));         // select only the columns you need
    }

    return c;
}

Maintenant, vous pouvez l'utiliser comme suit:

var c = equijoin(userProfiles, questions, "id", "createdBy", function (a, b) {
    return {
        id: b.id,
        text: b.text,
        name: a.name
    };
});

Voir la démo ici: http://jsfiddle.net/zvHVv/1/

17voto

Joseph Myers Points 3267

Cela semble être un important usage général en question, et bien qu'il existe beaucoup de réponses, certains ont un comportement borderline comme la modification de données existantes, de résoudre complètement le problème est différent de la question à portée de main, à l'aide de jusqu'à 93,057 octets de JavaScript (pour ne pas mentionner produire le mauvais résultat), la production de trop complexe supplémentaire à l'imbrication de structures de données, nécessitant beaucoup de code à chaque invocation, et plus sérieusement, n'étant pas une solution autonome pour la important à des fins plus générales problème au cœur de cette question.

Donc, pour le meilleur ou pour le pire, j'ai écrit une cale qui s'étend le JavaScript Array objet avec une méthode .joinWith destiné à être utilisé dans le but de rejoindre this tableau d'objets avec that tableau d'objets, by d'une indexation commune de champ. Il est possible d' select une liste de champs désiré en sortie (bon pour la fusion des tableaux d'objets avec de nombreux champs lorsque seuls quelques-uns sont voulu) ou d' omit d'une liste de champs en sortie (bon pour la fusion des tableaux d'objets lorsque la plupart des champs sont souhaitées, mais quelques-uns ne le sont pas).

La cale code n'est pas fait pour faire joli, de sorte qu'il sera à la fin, avec l'exemple de la façon de l'utiliser pour l'OP du type particulier de données d'entrée en première:

/* 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, i.e.,
 *       does exactly what you need. I haven't seen your actual data, so it's
 *       possible that your IDs are are not in common, (i.e., 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.
 *
 */

Le code suivant est à des fins de démonstration:

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"}]*

Il y a quelques autres belles choses cette solution n' (l'un d'eux est de préserver la capacité d'accéder aux données obtenues par sa clé d'indexation, en dépit de renvoyer un tableau).

Profitez-en!

/* 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;
        }
    }();
}

Documentation:

        /* this and that both refer to an array of objects, each containing
           object[by] as one of their fields */
        /*
         N.B. 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.
        */

7voto

pebbl Points 8909

Si c'était moi, j'aborderais ceci de la manière suivante:

La mise en place:

 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} );
 

Premièrement, il faudrait créer un objet de recherche, où l'identifiant de liaison est utilisé comme clé.

 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');
 

Maintenant que vous avez cela, il devrait être facile de parcourir les questions et de trouver votre objet utilisateur à fusionner:

 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
    });
  }
}
 

Vous devriez maintenant avoir tout ce dont vous avez besoin en result

 console.log(result);
 

7voto

Anton Points 3371

j'ai à peu près toujours utiliser underscore.js c'est qu'il a un bon support pour les tableaux et la "carte de réduction" pour qui ce problème peut être résolu avec.

voici un violon avec une solution à votre question ( on suppose qu'il existe une seule question par l'utilisateur comme l'original de votre post l'indique)

http://jsfiddle.net/x5Z7f/

(ouvrir le navigateur de la console pour voir le résultat)

    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) });

la réponse ci-dessus suppose que vous utilisez id == createdBy comme la colonne de jointure.

3voto

Ashok Damani Points 2299

tout ce que vous voulez, c'est le ResultArray calculé ci-dessous:

     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>")
    }
 

démo: http://jsfiddle.net/VqmVv/

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X