Ik heb een zeer eenvoudige JavaScript array die al dan niet duplicaten bevat.
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
Ik moet de duplicaten verwijderen en de unieke waarden in een nieuwe array zetten.
Ik zou kunnen wijzen op alle codes die ik'heb geprobeerd maar ik denk dat het'nutteloos is omdat ze niet werken. Ik accepteer ook jQuery oplossingen.
Met behulp van de Set constructor en de spread syntax:
uniq = [...new Set(array)];
uniqueArray = a.filter(function(item, pos) {
return a.indexOf(item) == pos;
})
In principe itereren we over de array en controleren we voor elk element of de eerste positie van dit element in de array gelijk is aan de huidige positie. Het is duidelijk dat deze twee posities verschillend zijn voor dubbele elementen. Door gebruik te maken van de 3e ("this array") parameter van de filter callback kunnen we een sluiting van de array variabele vermijden:
uniqueArray = a.filter(function(item, pos, self) {
return self.indexOf(item) == pos;
})
function uniq(a) {
var seen = {};
return a.filter(function(item) {
return seen.hasOwnProperty(item) ? false : (seen[item] = true);
});
}
Dit is hoe het meestal wordt gedaan. Het idee is om elk element in een hashtable te plaatsen en dan onmiddellijk te controleren op zijn aanwezigheid. Dit geeft ons lineaire tijd, maar heeft ten minste twee nadelen:
uniq([1,"1"])
zal alleen [1]
teruggevenuniq([{foo:1},{foo:2}])
geeft alleen [{foo:1}]
terug.
Dat gezegd hebbende, als je arrays alleen primitieven bevatten en je je niets aantrekt van types (b.v. het zijn altijd getallen), dan is deze oplossing optimaal.
Het beste uit twee wereldenEen universele oplossing combineert beide benaderingen: het gebruikt hash lookups voor primitieven en lineair zoeken voor objecten.
function uniq(a) {
var prims = {"boolean":{}, "number":{}, "string":{}}, objs = [];
return a.filter(function(item) {
var type = typeof item;
if(type in prims)
return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true);
else
return objs.indexOf(item) >= 0 ? false : objs.push(item);
});
}
Een andere optie is om de array eerst te sorteren, en dan elk element gelijk aan het voorgaande te verwijderen:
function uniq(a) {
return a.sort().filter(function(item, pos, ary) {
return !pos || item != ary[pos - 1];
})
}
sort
). Bovendien veranderen we stilletjes de originele array als neveneffect - niet goed! Echter, als je invoer al gesorteerd is, is dit de manier om te gaan (verwijder gewoon sort
uit het bovenstaande).
Uniek door...Soms is het gewenst om een lijst te uniquificeren op basis van andere criteria dan alleen gelijkheid, bijvoorbeeld, om objecten uit te filteren die verschillend zijn, maar een eigenschap delen. Dit kan elegant gedaan worden door een callback door te geven. Deze "key" callback wordt toegepast op elk element, en elementen met gelijke "keys" worden verwijderd. Aangezien verwacht wordt dat key
een primitieve teruggeeft, zal een hashtabel hier prima werken:
function uniqBy(a, key) {
var seen = {};
return a.filter(function(item) {
var k = key(item);
return seen.hasOwnProperty(k) ? false : (seen[k] = true);
})
}
Een bijzonder bruikbare key()
is JSON.stringify
die objecten zal verwijderen die fysiek verschillend zijn, maar er hetzelfde uitzien:
a = [[1,2,3], [4,5,6], [1,2,3]]
b = uniqBy(a, JSON.stringify)
console.log(b) // [[1,2,3], [4,5,6]]
Als de sleutel
niet primitief is, moet je lineair zoeken:
function uniqBy(a, key) {
var index = [];
return a.filter(function (item) {
var k = key(item);
return index.indexOf(k) >= 0 ? false : index.push(k);
});
}
In ES6 kun je een Set
gebruiken:
function uniqBy(a, key) {
let seen = new Set();
return a.filter(item => {
let k = key(item);
return seen.has(k) ? false : seen.add(k);
});
}
of een Map
:
function uniqBy(a, key) {
return [
...new Map(
a.map(x => [key(x), x])
).values()
]
}
Bij het verwijderen van objecten volgens een sleutel, wil je misschien de eerste van "equal" objecten behouden of de laatste.
Gebruik de Set
variant hierboven om de eerste te behouden, en de Map
om de laatste te behouden:
function uniqByKeepFirst(a, key) {
let seen = new Set();
return a.filter(item => {
let k = key(item);
return seen.has(k) ? false : seen.add(k);
});
}
function uniqByKeepLast(a, key) {
return [
...new Map(
a.map(x => [key(x), x])
).values()
]
}
//
data = [
{a:1, u:1},
{a:2, u:2},
{a:3, u:3},
{a:4, u:1},
{a:5, u:2},
{a:6, u:3},
];
console.log(uniqByKeepFirst(data, it => it.u))
console.log(uniqByKeepLast(data, it => it.u))
Zowel underscore als Lo-Dash bieden uniq
methoden. Hun algoritmes zijn in principe gelijk aan de eerste snippet hierboven en komen op het volgende neer:
var result = [];
a.forEach(function(item) {
if(result.indexOf(item) < 0) {
result.push(item);
}
});
Dit is kwadratisch, maar er zijn leuke extra goodies, zoals het verpakken van native indexOf
, de mogelijkheid om te uniqificeren op een sleutel (iteratee
in hun jargon), en optimalisaties voor reeds gesorteerde arrays.
Als je jQuery gebruikt en niet tegen iets kunt zonder een dollar ervoor, gaat het als volgt:
$.uniqArray = function(a) {
return $.grep(a, function(item, pos) {
return $.inArray(item, a) === pos;
});
}
Functie aanroepen zijn duur in JavaScript, daarom zijn de bovenstaande oplossingen, hoe beknopt ze ook zijn, niet bijzonder efficiënt. Voor maximale prestaties vervang je filter
door een lus en verwijder je de andere functie-aanroepen:
function uniq_fast(a) {
var seen = {};
var out = [];
var len = a.length;
var j = 0;
for(var i = 0; i < len; i++) {
var item = a[i];
if(seen[item] !== 1) {
seen[item] = 1;
out[j++] = item;
}
}
return out;
}
Dit stuk lelijke code doet hetzelfde als snippet #3 hierboven, maar dan een orde van grootte sneller (vanaf 2017 is het nog maar twee keer zo snel'- de mensen van JS core doen geweldig werk)!
function uniq(a) {
var seen = {};
return a.filter(function(item) {
return seen.hasOwnProperty(item) ? false : (seen[item] = true);
});
}
function uniq_fast(a) {
var seen = {};
var out = [];
var len = a.length;
var j = 0;
for(var i = 0; i < len; i++) {
var item = a[i];
if(seen[item] !== 1) {
seen[item] = 1;
out[j++] = item;
}
}
return out;
}
/////
var r = [0,1,2,3,4,5,6,7,8,9],
a = [],
LEN = 1000,
LOOPS = 1000;
while(LEN--)
a = a.concat(r);
var d = new Date();
for(var i = 0; i < LOOPS; i++)
uniq(a);
document.write('<br>uniq, ms/loop: ' + (new Date() - d)/LOOPS)
var d = new Date();
for(var i = 0; i < LOOPS; i++)
uniq_fast(a);
document.write('<br>uniq_fast, ms/loop: ' + (new Date() - d)/LOOPS)
ES6 biedt het Set object, wat de dingen een stuk eenvoudiger maakt:
function uniq(a) {
return Array.from(new Set(a));
}
of
let uniq = a => [...new Set(a)];
Een "lazy", generator-gebaseerde versie van uniq
kan op dezelfde basis worden gebouwd:
function* uniqIter(a) {
let seen = new Set();
for (let x of a) {
if (!seen.has(x)) {
seen.add(x);
yield x;
}
}
}
// example:
function* randomsBelow(limit) {
while (1)
yield Math.floor(Math.random() * limit);
}
// note that randomsBelow is endless
count = 20;
limit = 30;
for (let r of uniqIter(randomsBelow(limit))) {
console.log(r);
if (--count === 0)
break
}
// exercise for the reader: what happens if we set `limit` less than `count` and why
Snel en vies met behulp van jQuery:
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
var uniqueNames = [];
$.each(names, function(i, el){
if($.inArray(el, uniqueNames) === -1) uniqueNames.push(el);
});
Vanilla JS: Duplicaten verwijderen met behulp van een Object als een Set
Je kunt altijd proberen het in een object te stoppen, en dan door de sleutels te itereren:
function remove_duplicates(arr) {
var obj = {};
var ret_arr = [];
for (var i = 0; i < arr.length; i++) {
obj[arr[i]] = true;
}
for (var key in obj) {
ret_arr.push(key);
}
return ret_arr;
}
Vanilla JS: Duplicaten verwijderen door reeds geziene waarden te volgen (order-safe)
Of, voor een order-veilige versie, gebruik een object om alle eerder geziene waarden op te slaan, en controleer de waarden hiermee voordat je ze toevoegt aan een array.
function remove_duplicates_safe(arr) {
var seen = {};
var ret_arr = [];
for (var i = 0; i < arr.length; i++) {
if (!(arr[i] in seen)) {
ret_arr.push(arr[i]);
seen[arr[i]] = true;
}
}
return ret_arr;
}
ECMAScript 6: Gebruik de nieuwe Set datastructuur (order-safe)
ECMAScript 6 voegt de nieuwe Set
Data-Structure toe, waarmee je waarden van elk type kunt opslaan. Set.values
retourneert elementen in insertie-volgorde.
function remove_duplicates_es6(arr) {
let s = new Set(arr);
let it = s.values();
return Array.from(it);
}
Voorbeeld gebruik:
a = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
b = remove_duplicates(a);
// b:
// ["Adam", "Carl", "Jenny", "Matt", "Mike", "Nancy"]
c = remove_duplicates_safe(a);
// c:
// ["Mike", "Matt", "Nancy", "Adam", "Jenny", "Carl"]
d = remove_duplicates_es6(a);
// d:
// ["Mike", "Matt", "Nancy", "Adam", "Jenny", "Carl"]