2016-06-10 8 views
3

Ich muss in der Lage sein, ein gemeinsames Element zwischen einer beliebigen Anzahl von Arrays zu finden. Zum Beispiel lassen Sie uns sagen, dass es ein Objekt wie folgt:Der effizienteste Weg, ein gemeinsames Objekt zwischen einer beliebigen Anzahl von Arrays zu finden

var obj = { 
    a: [ 15, 23, 36, 49, 104, 211 ], 
    b: [ 9, 12, 23 ], 
    c: [ 11, 17, 18, 23, 38 ], 
    d: [ 13, 21, 23, 27, 40, 85] 
}; 

Ich brauche würde das gemeinsame Element zwischen jedem dieser Arrays zu bestimmen. (In diesem Fall 23).

Meine Lösung besteht darin, das kürzeste Array zu finden und jedes Element darin zu durchlaufen und den Index der anderen Arrays zu überprüfen.

var shortest = {}; 
var keys = []; 
for (var key in obj) { 

    if (obj.hasOwnProperty(key) && Array.isArray(obj[ key ])) { 
    keys.push(key); 

    if (!shortest.hasOwnProperty('length') || obj[ key ].length < shortest.length) { 
     shortest.name = key; 
     shortest.length = obj[ key ].length; 
    } 
    } 
} 



var res = obj[ shortest.name ].filter(function (v) { 

    for (var i = 0; i < keys.length; i++) { 

    if (obj[ keys[ i ] ].indexOf(v) === -1) { 
     return false; 
    } 

    return true; 
    } 
}; 

Dies scheint jedoch enorm ineffizient, und ich versuche, um zu bestimmen, ob es eine bessere Art und Weise ist, vorzugsweise ohne mehrmals durch die Dinge zu Schleife.

+3

So sind alle beliebigen Arrays passieren in Ihrem Beispielcode sortiert werden. Ist das Zufall, oder ist es eine Tatsache? – Quirk

+0

@Quik Happenstance. Ich glaube, dass die Datenquelle sortiert zurückkommt, aber ich zögere, darauf zu zählen. –

+2

Wenn alle Arrays sortiert sind, ist die Worst-Case-Laufzeit wahrscheinlich linear in der Summe ihrer Größen, dh 'O (n1 + n2 + ...)' – Quirk

Antwort

2

Ich glaube nicht, dass es möglich ist, dies in weniger als O(N) zu tun, wobei N die Anzahl der Elemente in allen Arrays ist. Ihre aktuelle Lösung ist weniger effizient, da indexOfO(N) für jedes Array ist und Sie alle für jedes Element im kürzesten Array durchlaufen können.

Ich denke, eine kartenbasierte Option O(N) wäre:

var counts = {}; 
var keys = Object.keys(obj); 
var arr, el; 
for (var k = 0; k < keys.length; k++) { 
    arr = obj[keys[k]]; 
    for (var i = 0; i < arr.length; i++) { 
     el = arr[i]; 
     if (counts[el] === undefined) { 
      // new value, start counting 
      counts[el] = 1; 
     } else { 
      // increment count for this value 
      counts[el]++; 
      // if we have as many values as keys, we're done 
      if (counts[el] === keys.length) { 
       return el; 
      } 
     } 
    } 
} 

Einige Einsprüche hier:

  • Dies setzt voraus, dass die Elemente verwendet werden können, als Objektschlüssel (dh sie eindeutig sein konvertiert in Strings), und Sie haben keine unangenehmen Randfälle wie 1 und "1" in verschiedenen Arrays.

  • Dies setzt voraus, dass die Array-Werte für jedes Array eindeutig sind.

  • Dies setzt voraus, dass es nur ein Element in der Kreuzung gibt.

https://jsfiddle.net/xzfa75og/

+2

Sie können es nicht in weniger als 'O (N)' sicher tun. Sie müssen mindestens alle Werte lesen, und das ist schon "O (N)". – Oriol

+2

Worst Case ist immer 'O (N) ', aber Sie müssen nicht alle Werte in den häufigen Fällen lesen, wenn Sie wissen, dass nur ein Element in der Schnittmenge ist. – nrabinowitz

+0

Das scheint genau das zu sein, was ich brauche. –

2

Eine weitere O(n) Vereinigungs Lösung, funktionell codiert. Die Einschränkung "Elemente als Objektschlüssel" gilt weiterhin, obwohl dadurch die Menge (Array) aller gemeinsam genutzten Elemente in der Schnittmenge zurückgegeben wird.

function common(o) { 
    // Map each of the object key arrays to a set. 
    return Object.keys(o).map(function(k) { 
    return o[k].reduce(function(a, e) { 
     a[e] = 1; 
     return a; 
    }, {}); 
    }).reduce(function(a, e) { 
    // Perform a set union. 
    Object.keys(e).forEach(function(k) { 
     if (!a[k]) { 
     delete e[k]; 
     } 
    }); 
    return e; 
    }) 
} 

var obj = { 
    a: [ 15, 23, 36, 49, 104, 211 ], 
    b: [ 9, 12, 23 ], 
    c: [ 11, 17, 18, 23, 38 ], 
    d: [ 13, 21, 23, 27, 40, 85] 
}; 

common(obj); 
+0

Dies sieht nicht wie O (n) aus. Ihre O (n) Zeit ist beendet, wenn Sie die Object.keys in Objekte mappen und dann weitere Reduzierungen und forEach Operationen durchführen. Sieht für mich wie O (2n) aus. Außerdem schlägt dieser Algorithmus auch für die doppelten Einträge fehl, beispielsweise wenn jedes Array zwei oder mehr Elemente als 23 hat, findet dies nur eines von ihnen. – Redu

+0

Ja, das setzt den Schnittpunkt. Wenn Sie ein Vielfaches (dh Taschen) zählen möchten, können Sie im Akkumulator zählen (ersetzen Sie "a [e] = 1" durch "a [e] ++", initialisieren Sie nach Bedarf) und nehmen Sie min (a [k ], e [k]) 'on' reduce'. 'O (n)' bedeutet einfach linear asymptotisches Wachstum. Mit dieser Notation gilt 'O (2n) = O (n)', da beide linear sind. Weitere Informationen finden Sie [hier] (https://en.wikipedia.org/wiki/Big_O_notation#Multiplication_by_a_constant). – arcyqwerty

0

Ich würde diesen Job durch eine Erfindung von Array.prototype.intersect() tun. Es wird nicht fehlschlagen, wenn doppelte Elemente im Array vorhanden sind. Wenn Sie dasselbe Element in jedem Array dupliziert haben, erhalten Sie das Duplikat in der Kreuzung, wie es sein sollte. Mal sehen, wie es

Array.prototype.intersect = function(...a) { 
 
     return [this,...a].reduce((p,c) => p.filter(e => c.includes(e))); 
 
    }; 
 
var obj = { 
 
      a: [ 15, 23, 36, 49, 104, 211 ], 
 
      b: [ 9, 12, 23 ], 
 
      c: [ 11, 17, 18, 23, 38 ], 
 
      d: [ 13, 21, 23, 27, 40, 85] 
 
      }, 
 
    arrs = Object.keys(obj).map(e => obj[e]); 
 

 
console.log(JSON.stringify(arrs.pop().intersect(...arrs))); // take the last one out to invoke the method over the rest

0

einfach eine andere Lösung in ES5 mit einem temporären Objekt in O (n) arbeiten.

var obj = { a: [15, 23, 36, 49, 104, 211], b: [9, 12, 23, 15], c: [11, 17, 18, 23, 38], d: [13, 21, 23, 27, 40, 85] }, 
 
    result = Object.keys(obj).reduce(function (o) { 
 
     return function (r, k, i) { 
 
      var t = o; 
 
      o = Object.create(null); 
 
      return obj[k].filter(function (a) { 
 
       return (!i || t[a]) && (o[a] = true); 
 
      }); 
 
     }; 
 
    }(Object.create(null)), []); 
 

 
console.log(result);

+2

Dies ist der einzige Algorithmus, der die Aufgabe in O (n) innerhalb der gegebenen Antworten erledigt. Ich denke, ich muss meine 'Array.prototype.intersect()' ändern, um den Filter-Callback als Methode zu verwenden, obwohl 'includes()' einfach cooler aussieht. – Redu

+0

aber 'includes()' funktioniert nicht in der Kante. –

+0

Sie sollten Ihre Romanze mit Edge wahrscheinlich noch einmal überdenken. Ich habe keine Ahnung, wie Edge eine Pull-Anforderung an Node beantragt hat. So gut wie nichts funktioniert mit Edge einschließlich Destrukturierung, Spread-Operator, etc etc Gott weiß was noch. Ich denke, dass das Beste Firefox ist (im Grunde mag ich ihre erstaunliche Dokumentation und hilft der Gemeinschaft) und ich habe nichts gesehen, das nicht an FF arbeitet. Chrome ist in Ordnung, hat aber gerade mit V50 begonnen, um eine NodeList durch einen Spread-Operator wie 'myArr = [... nodeList]' in ein Array konvertieren zu können. Aber warum Edge ..? – Redu