2009-12-11 13 views
9

Come tutti sanno non c'è alcuna funzione incorporata per rimuovere i duplicati da un array in JavaScript. Ho notato che manca anche in jQuery (che ha una funzione unica solo per le selezioni DOM), e lo snippet più comune che ho trovato controlla l'intero array e un sottoinsieme di esso per ogni elemento (non molto efficiente, penso), come :unico() per array in JavaScript

for (var i = 0; i < arr.length; i++) 
    for (var j = i + 1; j < arr.length; j++) 
     if (arr[i] === arr[j]) 
      //whatever 

così ho fatto il mio:

function unique (arr) { 
    var hash = {}, result = []; 
    for (var i = 0; i < arr.length; i++) 
     if (!(arr[i] in hash)) { //it works with objects! in FF, at least 
      hash[arr[i]] = true; 
      result.push(arr[i]); 
     } 
    return result; 
} 

mi chiedo se c'è qualche altro algoritmo accettato come il migliore per questo caso (o se si vede alcun difetto evidente che poteva essere risolto), o , cosa fai quando hai bisogno di questo in javascript (sono consapevole che jQuery non è l'unico framework e alcuni altri potrebbero averlo già trattato).

+1

fare queste serie contiene solo valori scalari, o c'è una possibilità che contenga oggetti e array? –

+0

E c'è l'ipotesi di ordinato o no? –

risposta

28

Utilizzando l'oggetto letterale è esattamente quello che vorrei fare. Un sacco di persone perdere questa tecnica un sacco del tempo, optando invece per allineamento tipico cammina come il codice originale che avete dimostrato. L'unica ottimizzazione sarebbe evitare la ricerca arr.length ogni volta. Oltre a questo, O (n) è tanto buono quanto si ottiene per l'unicità ed è molto meglio dell'esempio originale di O (n^2).

function unique(arr) { 
    var hash = {}, result = []; 
    for (var i = 0, l = arr.length; i < l; ++i) { 
     if (!hash.hasOwnProperty(arr[i])) { //it works with objects! in FF, at least 
      hash[ arr[i] ] = true; 
      result.push(arr[i]); 
     } 
    } 
    return result; 
} 

// * Edited to use hasOwnProperty per comments 

complessità di tempo per riassumere

f() | unsorted | sorted | objects | scalar | library 
____________________________________________________________ 
unique | O(n) | O(n) | no | yes | n/a 
original | O(n^2) | O(n^2) | yes | yes | n/a 
uniq  | O(n^2) | O(n) | yes | yes | Prototype 
_.uniq | O(n^2) | O(n) | yes | yes | Underscore 

Come con la maggior parte degli algoritmi, ci sono compromessi. Se si stanno solo ordinando valori scalari, le modifiche all'algoritmo originale forniscono la soluzione ottimale. Tuttavia, se è necessario ordinare i valori non scalari, utilizzare o imitare il metodo uniq di una delle librerie descritte sarà la scelta migliore.

+5

È meglio usare 'hash.hasOwnProperty (arr [i])'. L'operatore 'in' restituisce true per proprietà ereditate come' toString'. '(" toString "in {}) => true' –

+0

Buona cattura @Chetan. Ho aggiornato la risposta. –

+0

La funzione 'unique' non ha anche la complessità O (n) per gli elenchi ordinati? – Xavi

4

penso che la versione non funziona quando si dovrà oggetti o funzione nella matrice che danno stringa di rappresentazione come [Object object]. Perché puoi avere solo stringhe come chiavi negli oggetti (nell'oggetto "hash" qui). Avrai bisogno di collegarti alla matrice dei risultati per scoprire se la nuova voce esiste già. Sarà ancora più veloce del primo metodo.

Il prototipo JS ha un metodo "uniq", potresti trarre ispirazione da esso.

+0

Buon punto, non ho considerato il problema 'toString'. –

+0

Il primo metodo non funziona con gli Oggetti , se ho capito bene, IOW === non funziona sugli oggetti, quindi presumendo che l'array conterrà solo "scalari" che possono essere confrontati direttamente con == o === (ad esempio, ints, float, bool, stringhe) d o pensi ancora che il secondo non funzioni? –

+0

e, aspetta. Immagino che == funzioni bene sui riferimenti agli oggetti. nm allora! –

1

io non sono un esperto di algoritmi con qualsiasi mezzo, ma ho tenuto d'occhio underscore.js. Hanno questo come una funzione chiamata uniq:

http://documentcloud.github.com/underscore/#uniq

ho guardato il codice nella loro biblioteca, e copiati qui per riferimento (non il mio codice, il codice appartiene a underscore.js):

// Produce a duplicate-free version of the array. If the array has already 
// been sorted, you have the option of using a faster algorithm. 
_.uniq = function(array, isSorted) { 
    return _.reduce(array, [], function(memo, el, i) { 
     if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo.push(el); 
     return memo; 
    }); 
}; 

EDIT: Hai bisogno di camminare per il resto del codice underscore.js, e ho quasi preso questo codice fuori a causa di esso. Ho lasciato lo snippet di codice nel caso in cui fosse ancora utile.

+0

Sono sicuro che! _. Include itera anche la matrice da zero. –

+0

Non avevo mai sentito parlare di questa libreria, quindi ho fatto una passeggiata attraverso il codice guardando specificamente '_.include' e' _.last'. Sembra che gli array ordinati prenderanno O (n) e unsorted sarà O (n^2), quindi non è un miglioramento costante. –

+0

Justin: buon investigatore. L'esempio del codice OPs (il primo) sembra presupporre che l'array sia ordinato. Avvia il ciclo interno dall'indice corrente + 1. –

0

Sfortunatamente gli oggetti JS non hanno identità accessibile dalla lingua - come altri locatori hanno menzionato, l'uso di oggetti come chiavi in ​​un dizionario fallisce quando oggetti diversi hanno rappresentazioni di stringa uguali e non c'è la funzione id() nella lingua.

Esiste un modo per evitare il controllo di tutte le coppie O (n^2) per l'identità === se è possibile modificare gli oggetti.Scegli una stringa casuale, cammina l'array una volta per verificare che nessun oggetto abbia una proprietà con quel nome, quindi fai semplicemente arr[i][randomPropertyName]=1 per ogni i. Se il prossimo oggetto dell'array ha già quella proprietà, allora è un duplicato.

Sfortunatamente, quanto sopra funzionerà solo per oggetti modificabili. Non riesce a valori di matrice che non consentono l'impostazione di proprietà (ad esempio, numeri interi, 42['random']=1 semplicemente non funziona :()

4

divertimento con il divertimento (finzionale)

function uniqueNum(arr) { 
    return Object.keys(arr.reduce(
     function(o, x) {o[x]=1; return o;}, {})).map(Number); 
}