2011-12-29 18 views
38
_.intersection([], []) 

funziona solo con tipi primitivi, giusto?Come utilizzare l'intersezione "underscore" sugli oggetti?

Non funziona con gli oggetti. Come posso farlo funzionare con gli oggetti (magari controllando il campo "Id")?

var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ] 
var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ] 

In questo esempio, il risultato dovrebbe essere:

_.intersection(a, b); 

[{ 'id': 1, 'name': 'Jake'}];

+23

È incredibile che nessuna di queste API consenta di passare una funzione di uguaglianza. – Pointy

risposta

24

È possibile creare un'altra funzione in base alla funzione di sottolineatura. Dovete solo cambiare una riga di codice dalla funzione originaria:

_.intersectionObjects = function(array) { 
    var slice = Array.prototype.slice; // added this line as a utility 
    var rest = slice.call(arguments, 1); 
    return _.filter(_.uniq(array), function(item) { 
     return _.every(rest, function(other) { 
     //return _.indexOf(other, item) >= 0; 
     return _.any(other, function(element) { return _.isEqual(element, item); }); 
     }); 
    }); 
    }; 

In questo caso si sarebbe ora utilizzare il metodo IsEqual() di sottolineatura al posto di uguaglianza di confronto di JavaScript. L'ho provato con il tuo esempio e ha funzionato. Ecco un estratto dalla documentazione di sottolineatura per quanto riguarda la funzione IsEqual:

_.isEqual(object, other) 
Performs an optimized deep comparison between the two objects, to determine if they should be considered equal. 

È possibile trovare la documentazione qui: http://documentcloud.github.com/underscore/#isEqual

ho messo il codice a jsFiddle modo da poter testare e confermare: http://jsfiddle.net/luisperezphd/jrJxT/

+4

Questo è 3 cicli così fondamentalmente O (n^3) sicuramente puoi fare meglio. (prova O (nlogn) – Raynos

+1

Buon punto Raynos, stavo cercando di andare il più possibile con quello che c'era già, questa era solo una piccola parte della funzione intersect() di underscore che menziona usando e quindi la performance di chi presumo che sia felice con. –

+0

Raynos, ho fornito un algoritmo alternativo al di sotto del quale * potreste * mi piace di più –

1
var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ]; 
var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ]; 

funzione di lavoro:

function intersection(a,b){ 
    var c=[]; 
    for(m in a){ 
     for(n in b){ 
     if((a[m].id==a[n].id)&&(a[m].name==b[n].name)) 
       c.push(a[m]);   
     }} 
    return c; 
    } 
console.log(intersection(a,b)); 

Ho anche provato il codice in jQuery specialmente dopo il suggerimento di punta. Il confronto deve essere personalizzabile secondo la struttura dell'oggetto JSON.

<script type="text/javascript"> 
jQuery(document).ready(function(){ 
    var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ]; 
    var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ]; 
    var c=[]; 
    jQuery.each(a, function(ka,va) { 
     jQuery.each(b, function(kb,vb) {  
       if(compare(va,vb)) 
        c.push(va); 
    }); 
    }); 
    console.log(c); 
}); 
function compare(a,b){ 
    if(a.id==b.id&&a.name==b.name) 
    return true; 
    else return false; 
} 
</script> 
+1

L'utilizzo di 'for ... in' su array non è una buona idea. – Pointy

+0

@Pointy. Quindi cosa si può fare? Qui, per ... in iterare tutti gli elementi in un oggetto. L'obiettivo di base qui è quello di confrontare tutti gli oggetti secondari con quelli di un altro. –

+0

Bene quando vengono passate le istanze di array, il codice può scorrere gli indici numerici. Il problema con "for ... in" è che può essere confuso perché includerà proprietà che potrebbero essere presenti nell'oggetto prototype di Array. – Pointy

3

Tecnicamente, funziona sugli oggetti, ma è necessario fare attenzione all'eguaglianza di riferimento.

var jake = {'id': 1, 'name': 'jake' }, 
    jenny = {'id':4, 'name': 'jenny'}, 
    nick = {'id': 9, 'name': 'nick'}; 
var a = [jake, jenny] 
var b = [jake, nick]; 

_.intersection(a, b); 
// is 
[jake] 
5

I metodi di matrice in sottolineatura sono molto potenti, si deve solo bisogno di poche righe per compiere ciò che si vuole fare:

var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ]; 
var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ]; 

var result = _(a).chain().map(function(ea) { 
    return _.find(b, function(eb) {return ea.id == eb.id;}); 
}).compact().value(); 

Se si dispone di grandi array si può sbarazzarsi del compact() chiama con una riga aggiuntiva:

var result = []; 
_.each(a, function(ea) { 
    var entry = _.find(b, function(eb) {return ea.id == eb.id;}); 
    if (entry) result.push(entry); 
}); 
+1

È quadratico nel tempo e ciò non è corretto – user2846569

+0

Vero, è O (N * M) .Dipendente dalla dimensione del set di dati (ad es. Hund articoli rossi) questo probabilmente non sarà affatto un problema.Se lo è, probabilmente avrai bisogno di un approccio completamente diverso: usa matrici ordinate, crea una sorta di indicizzazione ... –

+0

Nella maggior parte dei casi hai ragione, ma potresti perdere il punto in cui i dati crescono e le prestazioni degradano e l'altra cosa è se hai un codice del genere nella maggior parte dei luoghi, diciamo che con 100 elementi è 100 volte più lento, e per la grande app potrebbe diventare evidente. Ma ovviamente ogni caso deve essere studiato separatamente. – user2846569

23

Ecco un algoritmo alternativo che dovrebbe essere flessibile e funzionare meglio. Uno di questi miglioramenti è che puoi specificare la tua funzione di confronto, quindi nel tuo caso puoi semplicemente confrontare l'id se si tratta di un identificativo univoco.

function intersectionObjects2(a, b, areEqualFunction) { 
    var results = []; 

    for(var i = 0; i < a.length; i++) { 
     var aElement = a[i]; 
     var existsInB = _.any(b, function(bElement) { return areEqualFunction(bElement, aElement); }); 

     if(existsInB) { 
      results.push(aElement); 
     } 
    } 

    return results; 
} 

function intersectionObjects() { 
    var results = arguments[0]; 
    var lastArgument = arguments[arguments.length - 1]; 
    var arrayCount = arguments.length; 
    var areEqualFunction = _.isEqual; 

    if(typeof lastArgument === "function") { 
     areEqualFunction = lastArgument; 
     arrayCount--; 
    } 

    for(var i = 1; i < arrayCount ; i++) { 
     var array = arguments[i]; 
     results = intersectionObjects2(results, array, areEqualFunction); 
     if(results.length === 0) break; 
    } 

    return results; 
} 

Si può usare in questo modo:

var a = [ { id: 1, name: 'jake' }, { id: 4, name: 'jenny'} ]; 
var b = [ { id: 1, name: 'jake' }, { id: 9, name: 'nick'} ]; 
var c = [ { id: 1, name: 'jake' }, { id: 4, name: 'jenny'}, { id: 9, name: 'nick'} ]; 

var result = intersectionObjects(a, b, c, function(item1, item2) { 
    return item1.id === item2.id; 
}); 

Oppure si può lasciare fuori la funzione e userà sottolinea la funzione _.isEqual(), in questo modo:

var result = intersectionObjects(a, b, c); 

Puoi trovarlo su jsFiddle qui: http://jsfiddle.net/luisperezphd/43vksdn6/

+0

Grazie, questo mi ha aiutato molto nel mio progetto attuale. –

+0

Ottima soluzione, grazie! – Fabio

+0

Risposta di BEst, perché dà la possibilità di filtrare sull'elemento ceratin – Lightning3

4

Mi piacerebbe condividere il mio gene ral soluzione per quei casi.

ho aggiunto una funzione generale per sottolineare, utilizzando mixin, che esegue un'operazione binaria 'allineamento' su due collezioni, secondo una data funzione hash: esempio

_.mixin({ 
    collectionOperation: function(arr1, arr2, hash, action) { 
     var iArr1 = _(arr1).indexBy(hash) 
      , iArr2 = _(arr2).indexBy(hash); 
     return action(_(iArr1).keys(), _(iArr2).keys()).map(function (id) { 
      return iArr1[id] || iArr2[id]; 
     }); 
    } 
}); 

utilizzati:

_([{id:1,v:'q'},{id:2,v:'p'}]).collectionOperation([{id:3,v:'pq'}], 'id', _.union) 

Si noti che 'id' può essere sostituito con una funzione.

Credo che questa soluzione sia O (n + m).

+0

Un test delle prestazioni sarebbe fantastico, qualcosa di simile (http://stackoverflow.com/a/12074451) http://jsfiddle.net/neoswf/aXzWw/ o jsperf .com – SDK

-2
var a = {a:'a1',b:'b1'}, 
    b = {a:'a2',b:'b2',c:'c2'}; 

_.pick(a,_.intersection(_.keys(a),_.keys(b))); 

// {a:'a1',b:'b1'} 
+1

Questo è sbagliato, il confronto viene eseguito solo sui tasti: _.keys (a) restituisce ["a", "b"] e _.keys (b) restituisce ["a", "b", " c "]. –

0

se si vuole confrontare gli oggetti:

b = {"1":{"prod":"fibaro"},"2":{"prod":"aeotec"},"3":{"prod":"sw"}}; 
a = {"1":{"prod":"fibaro"}}; 


_.intersectObjects = function(a,b){ 
    var m = Object.keys(a).length; 
    var n = Object.keys(b).length; 
    var output; 
    if (m > n) output = _.clone(a); else output = _.clone(b); 

    var keys = _.xor(_.keys(a),_.keys(b)); 
    for(k in keys){ 
     console.log(k); 
     delete output[keys[k]]; 
    } 
    return output; 
} 
_.intersectObjects(a,b); // this returns { '1': { prod: 'fibaro' } } 
3

In lodash 4.0.0. Possiamo provare come questo

var a = [ {'id': 1, 'name': 'jake' }, {'id':4, 'name': 'jenny'} ]; 
var b = [ {'id': 1, 'name': 'jake' }, {'id': 9, 'name': 'nick'} ]; 

_.intersectionBy(a, b, 'id'); 

uscita:

[{ 'id': 1, 'name': 'Jake'}];

+0

Sì, molto più facile in questi giorni con l'ultimo lodash! – Rodney

+0

Infine, passare da underscore a lodash – claytronicon

Problemi correlati