ridurre i duplicati in un oggetto JavaScript
-
30-09-2019 - |
Domanda
Ho un oggetto come:
{
a : 'foo',
b : 'bar',
c : 'foo',
d : 'baz',
e : 'bar'
}
Voglio ridurre i duplicati come:
{
ac : 'foo',
be : 'bar',
d : 'baz'
}
Che cosa è un buon modo per farlo?
Qualche distinguo:
- Non ci sarà sempre e solo un piccolo numero di coppie. (Attualmente ci sono 7; potevo immaginare di andare fino a, diciamo, 20).
- I nomi delle proprietà iniziali sarà sempre solo un singolo carattere, come nell'esempio
- I valori potrebbero potenzialmente funzionare a diverse centinaia di caratteri.
- sia la velocità e il codice di lunghezza sono molto importanti, ma dato il piccolo numero di righe, codice chiarezza è probabilmente ancora più importante.
Soluzione
passare attraverso ogni proprietà dell'oggetto e costrutto altro oggetto, dove le chiavi sono i valori della prima, ed i valori sono liste di chiavi (dal primo). Poi si torna indietro attraverso quella secondo oggetto e rendere il risultato finale.
Qualcosa di simile a questo:
function noDupes(obj) {
var o2 = {};
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
var list = o2[obj[k]] || [];
list.push(k);
o2[obj[k]] = list;
}
}
var rv = {};
for (k in o2) {
if (o2.hasOwnProperty(k))
rv[o2[k].join('')] = k;
}
return rv;
}
Ora, se i valori dell'oggetto originale non sono stringhe, allora le cose si fanno più coinvolti: solo le stringhe possono essere le chiavi di proprietà in un oggetto JavaScript. Si potrebbe guardarsi intorno per un'implementazione più generale hash, in quel caso. Se gli oggetti tendono ad essere piuttosto piccole (meno di 10 o giù di lì proprietà) si potrebbe scrivere il n 2 versione, dove è sufficiente un'iterazione sulle proprietà e poi iterare ancora per ciascuno di essi. Che probabilmente sarebbe una cattiva idea se gli oggetti potrebbero essere di grandi dimensioni e si deve eseguire questa operazione molto.
Altri suggerimenti
var Reduce = function(obj)
{
var temp = {};
var val = "";
for (var prop in obj)
{
val = obj[prop];
if (temp[val])
temp[val] = temp[val] + prop.toString();
else
temp[val] = prop.toString();
}
var temp2 = {};
for (var prop in temp)
{
val = temp[prop];
temp2[val] = prop.toString();
}
return temp2;
};
Usa come:
var obj = {
a :"foo",
b : "bar",
c : "foo",
d : "bar",
e : "bar"
};
var ob2 = Reduce(obj);
Questa è la più breve ho potuto farlo:
var obj, newObj = {}; // obj is your original
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
for (var j in newObj) {
if (newObj.hasOwnProperty(j) && newObj[j] === obj[i]) break;
j = "";
}
newObj[i + j] = obj[i];
j && delete newObj[j];
}
Spiegazione:
- itera ogni elemento nell'oggetto originale,
obj
, e produce un nuovo oggetto,newObj
. - Per ogni elemento in originale, cerca la
newObj
mezza prodotta per lo stesso valore. - Il risultato èj
, sia il nome della proprietà, se trovato, o una stringa vuota se non è. - In entrambi i casi, il nuovo oggetto richiede una proprietà del stesso nome della proprietà corrente nell'oggetto originale, più questo valore di
j
. - Si elimina anche la proprietà trovata in
newObj
se ci fosse uno, per evitare i duplicati in costruzione.
Certo, impostazione j = ""
all'interno del ciclo è inefficiente. Questo può essere facilmente sostituito con un secondo insieme variabile ""
inizialmente, e j
solo se viene trovata una corrispondenza. Ho deciso di andare per la semplicità però.
Senza un maggiore genere solo ciclo libreria ciascuna coppia (uso hasOwnProperty
) e aggiungere / append la chiave per un istogramma in cui la chiave istogramma è il valore coppia e dei valori dell'istogramma sono le chiavi concatenati. Poi invertire il tasto / valori dell'istogramma.
Modifica:. Se i valori iniziali non sono stringhe (e non reversibile mappa) poi una libreria 'identità hash' esistente potrebbe ancora consentire l'approccio di cui sopra al lavoro
In alternativa, è possibile associare a dire, [[k,v],...]
e ordinamento e quindi utilizzare un approccio simile a un bucket sort (immagino che sia già ordinato) a valori di unione di "chiavi uguali" nel passaggio di uscita.
Può andare in questo modo (mentre il codice può avere bug, l'approccio è suono - funzionerà anche con oggetti arbitrari come valori fino a quando si dispone di un modo per confrontare i valori):
var _f = []
for (var k in map) {
if (map.hasOwnProperty(k)) {
_f.push({k: k, v: map[k]})
}
}
// you could also sort on name (a.k), if it's important
// this makes it more versatile and deterministic in output
// ordering than the histogram method above
var f = _f.sort(function (a, b) { return a.v < b.v ? 1 : a.v > b.v ? -1 : 0 })
var res = {}
var prev
var name = ""
// after the sort all {k:,v:} objects with the same values will be grouped
// together so we only need to detect the change to the next value
// and everything prior to that gets the merged key
for (var i = 0; i < f.length; i++) {
var p = f[i]
if (prev != p.v && name) {
res[name] = prev
name = ""
} else {
name = name + p.k
}
prev = p.v
}
if (name) { // don't forget last set of values
res[name] = prev
}
// have res
Perdonami se sono completamente fuori, ma mi sembra che il modo in cui si sta combinando questi, hai le chiavi ed i valori nel modo sbagliato. Che dire di questo?
{
'foo': ['a', 'c'],
'bar': ['b', 'e'],
'baz': ['d']
}
Dovrebbe essere abbastanza facile da convertire:
flippedObj = {};
for (var letter in obj) {
if (obj.hasOwnProperty(letter)) {
var letters = flippedObj[obj[letter]];
if (letters === undefined) {
letters = [];
flippedObj[obj[letter]] = letters;
}
letters.push(letter);
}
}
(Brain-compilato,. Ci potrebbe essere un paio di errori)
Inizia con un ridurre che utilizza un dizionario di contare i tag in modo capovolto. Modo molto performante in quanto utilizza il costruito nel dizionario sostegno, non per i cicli ecc.
var flipped = Object.keys(input).reduce(function(a,b){
var tag = input[b];
a[tag] = (a[tag] || '') + b;
return a;
}, {});
Restituisce un oggetto con il formato capovolto:
// {foo: "ac", bar: "be", baz: "d"}
Poi basta capovolgere il formato:
Object.keys(flipped).reduce(function(a,b){
a[flipped[b]]=b;
return a;
}, {});
Output:
// {ac: "foo", be: "bar", d: "baz"}