Jeg har et meget simpelt JavaScript-array, som måske eller måske ikke indeholder dubletter.
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
Jeg har brug for at fjerne dubletterne og sætte de unikke værdier i et nyt array.
Jeg kunne pege på alle de koder jeg har prøvet, men jeg tror det er ubrugeligt, fordi de ikke virker. Jeg accepterer også jQuery-løsninger.
Brug af konstruktøren Set og spread syntaksen:
uniq = [...new Set(array)];
uniqueArray = a.filter(function(item, pos) {
return a.indexOf(item) == pos;
})
I bund og grund itererer vi over arrayet og kontrollerer for hvert element, om den første position af dette element i arrayet er lig med den aktuelle position. Det er klart, at disse to positioner er forskellige for dubletter af elementer. Ved hjælp af den tredje parameter ("this array") i filter-callbacken kan vi undgå en lukning af array-variablen:
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);
});
}
Det er sådan, det normalt gøres. Ideen er at placere hvert element i en hashtabel og derefter kontrollere, om det er til stede med det samme. Dette giver os lineær tid, men har mindst to ulemper:
uniq([1,"1"])
vil kun returnere [1]
uniq([{foo:1},{foo:2}])
vil kun returnere [{foo:1}]
.
Når det er sagt, hvis dine arrays kun indeholder primitives, og du er ligeglad med typer (f.eks. er det altid tal), er denne løsning optimal.
Det bedste fra to verdenerEn universel løsning kombinerer begge tilgange: den bruger hash-opslag for primitives og lineær søgning for objekter.
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);
});
}
En anden mulighed er at sortere arrayet først og derefter fjerne hvert element, der er lig med det foregående:
function uniq(a) {
return a.sort().filter(function(item, pos, ary) {
return !pos || item != ary[pos - 1];
})
}
sort
). Desuden ændrer vi stille og roligt det oprindelige array som en sideeffekt - ikke godt! Men hvis dit input allerede er sorteret, er dette den rigtige måde at gøre det på (fjern blot sort
fra ovenstående).
Unik af...Nogle gange er det ønsket at unificere en liste baseret på nogle andre kriterier end blot lighed, f.eks. for at filtrere objekter fra, som er forskellige, men som deler en eller anden egenskab. Dette kan gøres elegant ved at overdrage en callback. Denne "key" callback anvendes på hvert element, og elementer med ens "keys" fjernes. Da key
forventes at returnere en primitiv, vil hashtabellen fungere fint her:
function uniqBy(a, key) {
var seen = {};
return a.filter(function(item) {
var k = key(item);
return seen.hasOwnProperty(k) ? false : (seen[k] = true);
})
}
En særlig nyttig key()
er JSON.stringify
, som vil fjerne objekter, der fysisk er forskellige, men "ser" ens ud":
a = [[1,2,3], [4,5,6], [1,2,3]]
b = uniqBy(a, JSON.stringify)
console.log(b) // [[1,2,3], [4,5,6]]
Hvis key
ikke er primitiv, må man ty til lineær søgning:
function uniqBy(a, key) {
var index = [];
return a.filter(function (item) {
var k = key(item);
return index.indexOf(k) >= 0 ? false : index.push(k);
});
}
I ES6 kan du bruge et Set
:
function uniqBy(a, key) {
let seen = new Set();
return a.filter(item => {
let k = key(item);
return seen.has(k) ? false : seen.add(k);
});
}
eller en Map
:
function uniqBy(a, key) {
return [
...new Map(
a.map(x => [key(x), x])
).values()
]
}
Når du fjerner objekter efter en nøgle, kan det være, at du ønsker at beholde det første af "lige" objekter eller det sidste af dem.
Brug ovenstående Set
-variant til at beholde det første og Map
til at beholde det sidste:
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))
Både underscore og Lo-Dash indeholder uniq
-metoder. Deres algoritmer ligner grundlæggende det første uddrag ovenfor og kan koges ned til dette:
var result = [];
a.forEach(function(item) {
if(result.indexOf(item) < 0) {
result.push(item);
}
});
Dette er kvadratisk, men der er dejlige ekstra godbidder, som f.eks. indpakning af native indexOf
, mulighed for at uniqify ved en nøgle (iteratee
i deres sprogbrug) og optimeringer for allerede sorterede arrays.
Hvis du bruger jQuery og ikke kan tåle noget uden en dollar foran, så går det sådan her:
$.uniqArray = function(a) {
return $.grep(a, function(item, pos) {
return $.inArray(item, a) === pos;
});
}
Funktionskald er dyre i JavaScript, og derfor er ovenstående løsninger, hvor kortfattede de end er, ikke særligt effektive. For at opnå maksimal ydeevne skal du erstatte filter
med en løkke og slippe for andre funktionskald:
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;
}
Denne klump grim kode gør det samme som uddrag #3 ovenfor, men en størrelsesorden hurtigere (fra 2017 er den kun dobbelt så hurtig - JS core-folkene gør et fantastisk stykke arbejde!)
ES6 indeholder Set objektet, som gør tingene meget nemmere:
function uniq(a) {
return Array.from(new Set(a));
}
eller
let uniq = a => [...new Set(a)];
En "doven", generatorbaseret version af uniq
kan bygges på samme grundlag:
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
Hurtig og beskidt ved hjælp af 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: Fjern dubletter ved hjælp af et objekt som et sæt
Du kan altid prøve at sætte det ind i et objekt og derefter iterere gennem dets nøgler:
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: Fjern dubletter ved at spore allerede sete værdier (ordre-safe)
Eller, for en ordre-sikker version, brug et objekt til at gemme alle tidligere sete værdier, og tjek værdierne mod det før før de tilføjes til et 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: Brug den nye Set-datastruktur (ordre-sikker)
ECMAScript 6 tilføjer den nye datastruktur Set
, som giver dig mulighed for at gemme værdier af enhver type. Set.values
returnerer elementer i indsættelsesrækkefølge.
function remove_duplicates_es6(arr) {
let s = new Set(arr);
let it = s.values();
return Array.from(it);
}
Eksempel på brug:
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"]