2016-05-23 36 views
7

Ho un array di oggetti simili alle seguenti:Rimozione oggetti equivalenti ma unici da un array JavaScript

var routeArr = [ 
    {start: 1, end: 2}, 
    {start: 1, end: 3}, 
    {start: 1, end: 4}, 
    {start: 2, end: 1}, 
    {start: 3, end: 1}, 
    {start: 4, end: 1} 
]; 

Questi oggetti rappresentano il punto di inizio e fine delle righe e, come tale, {start: 1, end: 2} e {start: 2, end: 1} rappresentano la stessa linea.

Sto cercando di rimuovere tutte le linee duplicate dalla matrice e non riesce a trovare un modo efficace o elegante per farlo. Ho provato un ciclo annidato ma, mi è stato detto che è una cattiva pratica (e sto ricevendo errori con la mia implementazione, ed è solo brutto).

for(var i = 0, numRoutes = routeArr.length; i < numRoutes; i++) { 
    var primaryRoute = routeArr[i]; 

    for(var j = 0; j < numRoutes; j++) { 
     var secondRoute = routeArr[j]; 

     if(primaryRoute.start === secondRoute.end && primaryRoute.end === secondRoute.start) { 
      routeArr.splice(j, 1); 
      continue; 
     } 
    } 
} 

Qualcuno può offrire suggerimenti?

+0

Il modo normale per farlo è: ordinarlo prima (nel tuo caso, dovresti invertire l'inizio e la fine se (fine> inizio)). Quindi le linee duplicate saranno esattamente uguali l'una con l'altra. Quindi basta fare un ciclo per rimuovere quello duplicato – Kelvin

+1

Quando rimuovi un elemento dell'array, non esegui mai il ciclo da 0 a lunghezza. non è sicuro perché dopo la rimozione devi aggiustare i tuoi indici. Meglio eseguire il ciclo in ordine decrescente, vale a dire dalla lunghezza da 1 a 0, questo funzionerà nel caso in cui si rimuoverà un elemento della matrice e non si otterranno mai elementi con indici più grandi.Anche la tua istruzione if controlla solo una condizione di essere una linea identica, devi aggiungere anche altri check o statement. – simon

risposta

3

Creare un oggetto/mappa in javascript e mantenere gli indici degli oggetti univoci, memorizzare "min (inizio, fine): max (inizio, fine)" come chiave e indice come valore. Ecco un'implementazione della vostra domanda in javascript:

// your initial array 
var routeArr = [ 
    {start: 1, end: 2}, 
    {start: 1, end: 3}, 
    {start: 1, end: 4}, 
    {start: 2, end: 1}, 
    {start: 3, end: 1}, 
    {start: 4, end: 1} 
]; 

// map where we will store key => value where key is a joined start,end of your array's item and value is an item index 
var keyToRouteIndexMap = {}; 

for (var i in routeArr){ 
    // calculating min and max from start and end to understand {start:1, end:2} and {start:2, end:1} object as duplicates 
    var min = Math.min(routeArr[i].start,routeArr[i].end); 
    var max = Math.max(routeArr[i].start,routeArr[i].end); 
    // unique key 
    var key = min+':'+max; 
    if (!keyToRouteIndexMap.hasOwnProperty(key)){ 
     keyToRouteIndexMap[key] = i; 
    } 
} 

for(var key in keyToRouteIndexMap){ 
    if(keyToRouteIndexMap.hasOwnProperty(key)){ 
     console.log(routeArr[keyToRouteIndexMap[key]]); 
    } 
} 
+1

Nota che se stai usando ES6, puoi invece usare [l'oggetto Set] (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) – Hamms

+0

C'è anche qualche implementazioni javascript create per imitare gli hashset: https://github.com/timdown/jshashtable – and0rsk

+0

@Hamms: come può essere d'aiuto l'oggetto Set? Confronta solo i riferimenti agli oggetti. –

2

Ecco una soluzione generale al problema della rimozione dei valori duplicati da array javascript:

/** 
* Takes an input array and returns a new array without identical elements. 
* 
* @param {array} input 
* @callback id identity function returning identical values for identical elements 
*/ 
function uniquify(input, id) { 
    result = []; 
    map = {}; 
    for (var i = 0, length = input.length; i < length; ++i) { 
     var element = input[i], identity = id(element); 
     if (!map.hasOwnProperty(identity)) { 
      result.push(element); 
      map[identity] = true; 
     } 
    } 
    return result; 
} 

applicati sulla data routeArr:

var routeArr = [ 
    {start: 1, end: 2}, 
    {start: 1, end: 3}, 
    {start: 1, end: 4}, 
    {start: 2, end: 1}, 
    {start: 3, end: 1}, 
    {start: 4, end: 1} 
]; 

routeArr = uniquify(routeArr, function(route) { 
    return route.start < route.end ? '' + route.start + ':' + route.end : '' + route.end + ':' + route.start; 
}); 
+0

Immagino che le funzioni di funzione di callback {start: 2, end: 3} e {start: 6, end: 1} nella stessa posizione. – Redu

+1

buona cattura, riparato. –

2

Si può fare in questo modo. Immagino che questo sia molto veloce poiché non ci sono ricerche. Un'operazione Array.prototype.reduce() per costruire contemporaneamente la tabella hash (tabella di ricerca) e l'oggetto ridotto. Quindi mappare le chiavi dell'oggetto per ottenere il risultato. Ecco qui;

var routeArr = [ 
 
    {start: 1, end: 2}, 
 
    {start: 1, end: 3}, 
 
    {start: 1, end: 4}, 
 
    {start: 2, end: 1}, 
 
    {start: 3, end: 1}, 
 
    {start: 4, end: 1} 
 
], 
 

 
reduced = routeArr.reduce((p,c) => {!(p[c.start+"-"+c.end] || p[c.end+"-"+c.start]) && (p[c.start+"-"+c.end] = c); 
 
            return p;},{}), 
 
result = Object.keys(reduced).map(e => reduced[e]); 
 
console.log(result);

Ebbene dare una seconda riflessione ho eliminato la porzione ridondante Object.keys(). Ora, questo non è altro che un singolo Array.prototype.reduce() pass completato nel solo O (n). Suppongo che questo potrebbe essere il massimo delle prestazioni. Controlla.

var routeArr = [ 
 
    {start: 1, end: 2}, 
 
    {start: 1, end: 3}, 
 
    {start: 1, end: 4}, 
 
    {start: 2, end: 1}, 
 
    {start: 3, end: 1}, 
 
    {start: 4, end: 1} 
 
], 
 

 
    reduced = routeArr.reduce((p,c) => {!(p[c.start+"-"+c.end] || 
 
              p[c.end+"-"+c.start]) && 
 
              (p[c.start+"-"+c.end] = true, 
 
              p.result.push(c)); 
 
              return p; 
 
             },{"result":[]}); 
 
console.log(reduced.result);

Ebbene sì ok Sono d'accordo sembra un po 'criptico, ma è molto semplice.

  • Stiamo utilizzando il metodo Array.prototype.reduce() con un valore iniziale qui. Questo è il nostro valore iniziale {"result":[]}. Quando riduciamo il nostro array routeArr, il nostro elemento iniziale con cui iniziare è ora un oggetto con una singola proprietà denominata risultato e valore di una matrice vuota.
  • riduzione è stato fornito con una funzione di callback anonimo che accetta due argomenti (p,c)p sta per precedente e c sta per corrente. Quindi, nella prima esecuzione, p è il nostro oggetto di inizializzazione, voglio dire che questo {"result":[]} e c è l'elemento nell'indice 0 dell'array (routeArr) che abbiamo chiamato riduci. Quindi nel primo turno c è {start: 1, end: 2}.
  • All'inizio di ogni round controlliamo se l'oggetto p contiene una proprietà che rappresenta i valori degli elementi correnti in entrambi gli ordini. Quindi il controllo arriva come questo !(p[c.start+"-"+c.end] || p[c.end+"-"+c.start]) che in termini umani significa "è vero che non hai una proprietà stringa come c.start-c.end o c.end-c.start". Quindi per esempio nella prima round the check è come "è vero che non hai una proprietà stringa come" 1-2 "o" 2-1 ". Se ha (false) non facciamo nulla ma se non lo è, eseguiamo il seguente azioni,..
  • && (p[c.start+"-"+c.end] = true, p.result.push(c)); return p; OK primi && lega le due istruzioni nelle parentesi alla condizione dell'istruzione precedente per valutare a true nel motore JS a && b istruzioni valuteranno solo b se a viene valutata a true Così avete ottenuto.. Ancora una volta in termini umani questo è ciò che accade ". È vero che non hai una proprietà stringa come" 1-2 "o" 2-1 "diventa vero e creiamo una proprietà" 1-2 "con un valore true . Quindi nei prossimi turni se incontriamo un 1-2 o un 2-1 non faremo nulla. Quindi spostiamo questo oggetto corrente nella proprietà result dello stesso oggetto (p.result) per diventare un rappresentante unico di tutti i suoi duplicati o gemelli. Quindi restituiamo p per una buona continuazione dei cicli di riduzione.

Spero sia chiaro.

+0

Bella soluzione funzionale. Ora mi piacerebbe davvero vedere un confronto delle prestazioni con gli approcci non funzionali. –

+0

Penso che ora hai esagerato un po 'con il codice di minification. Scegliere nomi variabili autodocumentanti e non mettere incarichi in confronti può aiutare OP a comprendere meglio la tua bella soluzione :) –

+0

@le_m sì, credo che tu abbia ragione. Metterò alcune spiegazioni qui sotto. Per me sembra però una poesia. :) – Redu

1

ho scritto la funzione qui sotto per farlo ordinatamente

var routeArr = [{ 
    start: 1, 
    end: 2 
}, { 
    start: 1, 
    end: 3 
}, { 
    start: 1, 
    end: 5 
}, { 
    start: 2, 
    end: 1 
}, { 
    start: 3, 
    end: 1 
}, { 
    start: 4, 
    end: 1 
}]; 

routeArr.IsDuplicate = function(obj) { 
    var i = this.length; 
    var count = 0 
    while (i--) { 
     if ((this[i].start === obj.start && this[i].end === obj.end) || (this[i].start === obj.end && this[i].end === obj.start)) { 
      count++; 
     } 
    } 
    return count>1; 
} 

for(var i = routeArr.length-1; i--;){ 
    if (routeArr.IsDuplicate(routeArr[i])) routeArr.splice(i, 1); 
} 
+0

http://codepen.io/mozzi/pen/bpXjjP ecco il codice funzionante –

+0

Questo è inefficiente dal punto di vista operativo. Richiede di valutare ogni coppia più volte. Ad esempio, se si aumenta la lunghezza della routeApp a 19, vengono effettuate 189 valutazioni. La mappa sembra essere un approccio più pulito. In Java, una Hashmap sarebbe una grande struttura dati per questo tipo di implementazione. https://github.com/timdown/jshashtable è un'implementazione hashset in javascript. – and0rsk

+0

Gli oggetti javascript offrono già un'implementazione 'hashmap', l'unico problema è che le chiavi non possono essere oggetti - quindi è necessaria una funzione 'hash' da una mappatura generale degli oggetti agli hash (il tuo link) che è inefficiente come dovresti iterare attraverso la catena del prototipo o da una funzione più performante fornita dall'utente. –

2

vostra metodologia ciclo nidificato è 'ugly'- ma che non è il problema.

Gli errori di implementazione derivano dal fatto che entrambi i cicli for presuppongono che la struttura dell'array non cambierà mentre la si sta modificando, causando la possibilità di saltare alcuni elementi dell'array.

"i" e "j" sono incrementi "stupidi" - Quel ciclo for non indica al codice di passare all'elemento successivo nell'array con ogni iterazione, lo dice per andare a (array[last_index_i_used+1] - Quindi quando si unire qualcosa alla matrice che stai osservando cambia, e il prossimo elemento in linea viene passato sopra.

Vedo un sacco di fantastici metodi di array e suggerimenti ES6, ma dalla tua domanda presumo che tu sia ancora un po 'nuovo a JS, e potresti usare alcuni elementi fondamentali per la costruzione del tempo (senza offesa intesa).

Prova una funzione di decremento ricorsiva:

function uniquify(inputArray, ind){ 
    var checkStart = inputArray[ind].start, checkEnd =inputArray[ind].end 
    for (var i=(ind-1);i > -1; --i){ 
     var thisStart = inputArray[i].start, thisEnd = inputArray[i].end 
     if ((thisStart == checkStart || thisStart == checkEnd) && (thisEnd == checkStart || thisEnd == checkEnd)){ 

      inputArray.splice(i,1) 
     } 
    } 

    --ind 
    if (ind > -1){ 
     uniquify(inputArray,ind) 
    } 
} 
uniquify(routeArr,routeArr.length -1); 

mi piace che meglio di un nidificato ciclo for come si sono mai colpendo lo stesso valore più spesso di quanto è necessario, che mantiene prestazioni costanti indipendentemente dalle dimensioni di il tuo array.

Ma potreste chiedervi se qualunque cosa stia definendo 'routeArr' sta facendo tutto ciò che sta facendo in un modo che è intelligente - nel migliore dei casi, sembra che sprechi memoria e CPU che memorizzano i dati in modo inefficiente.

+0

Raccomando di usare il punto e virgola ovunque, anche se non sono sempre necessari in JS. Inoltre, il tuo indice di loop i è una variabile globale, meglio renderlo locale con var. –

+0

Hai ragione a supporre che io sia nuovo a JS, almeno in un progetto di queste dimensioni. Per farla breve, stiamo riscrivendo una vecchia app C++ per l'esecuzione su iPad e Android. È stata presa la decisione di utilizzare HTML5 e Javascript con Cordova (che inizialmente ho supportato ma, ora ho i miei dubbi su). Sfortunatamente, alcune decisioni di progettazione sono state prese nella precedente versione C++ che ci obbliga a lavorare con i dati dell'applicazione in modi non convenzionali. Sto cercando di trovare una soluzione elegante ed efficiente. Grazie per la tua risposta, ci sto provando mentre parliamo. – ewokthegreat

+0

@le_m - Grazie per aver notato la mancanza di var nel mio ciclo. Modificato per risolvere. –