Imam zelo preprosto polje v javascriptu, ki lahko vsebuje podvojitve ali pa tudi ne.
var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
Odstraniti moram podvojene vrednosti in edinstvene vrednosti vnesti v novo polje.
Lahko bi pokazal na vse kode, ki sem jih poskusil, vendar mislim, da so neuporabne, ker ne delujejo. Sprejemam tudi rešitve jQuery.
Uporaba konstruktorja Set in sintakse spread syntax:
uniq = [...new Set(array)];
uniqueArray = a.filter(function(item, pos) {
return a.indexOf(item) == pos;
})
V bistvu iteriramo po polju in za vsak element preverimo, ali je prvi položaj tega elementa v polju enak trenutnemu položaju. Očitno sta ta dva položaja različna za podvojene elemente. Z uporabo tretjega ("this array") parametra povratnega klica filtra se lahko izognemo zapiranju spremenljivke array:
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);
});
}
To je način, kako se to običajno naredi. Ideja je, da se vsak element postavi v tabelo hashtable in nato takoj preveri njegova prisotnost. To nam zagotavlja linearni čas, vendar ima vsaj dve pomanjkljivosti:
uniq([1,"1"])
vrne samo [1]
uniq([{foo:1},{foo:2}])
bo vrnil samo [{foo:1}]
.
Če vaše nizi vsebujejo samo primitive in vam ni mar za tipe (npr. vedno gre za števila), je ta rešitev optimalna.
Najboljše iz dveh svetovUniverzalna rešitev združuje oba pristopa: za primitive uporablja hash lookups, za objekte pa linearno iskanje.
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);
});
}
Druga možnost je, da najprej razvrstimo polje in nato odstranimo vsak element, ki je enak prejšnjemu:
function uniq(a) {
return a.sort().filter(function(item, pos, ary) {
return !pos || item != ary[pos - 1];
})
}
sort
). Poleg tega kot stranski učinek tiho spremenimo prvotno polje - kar ni dobro! Če pa je vaš vhodni podatek že razvrščen, je to pravi način (le odstranite sort
iz zgornjega podatka).
Edinstveno po...Včasih je zaželeno, da seznam unificiramo na podlagi drugih meril, ne le enakosti, na primer, da izločimo predmete, ki so različni, vendar si delijo neko lastnost. To je mogoče narediti elegantno s posredovanjem povratnega klica. Ta povratni klic "ključ" se uporabi za vsak element, elementi z enakimi "ključi" pa se odstranijo. Ker se pričakuje, da bo ključ
vrnil primitivno vrednost, bo hash tabela tu delovala dobro:
function uniqBy(a, key) {
var seen = {};
return a.filter(function(item) {
var k = key(item);
return seen.hasOwnProperty(k) ? false : (seen[k] = true);
})
}
Posebej uporaben ključ()
je JSON.stringify
, ki odstrani predmete, ki so fizično različni, vendar "izgledajo" enako:
a = [[1,2,3], [4,5,6], [1,2,3]]
b = uniqBy(a, JSON.stringify)
console.log(b) // [[1,2,3], [4,5,6]]
Če ključ
ni primitiven, se morate zateči k linearnemu iskanju:
function uniqBy(a, key) {
var index = [];
return a.filter(function (item) {
var k = key(item);
return index.indexOf(k) >= 0 ? false : index.push(k);
});
}
V ES6 lahko uporabite množico 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);
});
}
ali Map
:
function uniqBy(a, key) {
return [
...new Map(
a.map(x => [key(x), x])
).values()
]
}
Pri odstranjevanju predmetov po ključu boste morda želeli obdržati prvega od "enakih" predmetov ali zadnjega.
Za ohranitev prvega objekta uporabite zgornjo različico Set
, za ohranitev zadnjega pa Map
:
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))
Tako underscore kot Lo-Dash zagotavljata metode uniq
. Njuni algoritmi so v osnovi podobni prvemu zgornjemu odlomku in se glasijo takole:
var result = [];
a.forEach(function(item) {
if(result.indexOf(item) < 0) {
result.push(item);
}
});
To je kvadratno, vendar so na voljo lepe dodatne dobrine, kot so ovijanje nativnega indexOf
, zmožnost poenotenja po ključu (iteratee
v njihovem jeziku) in optimizacije za že razvrščene polja.
Če uporabljate jQuery in ne prenesete ničesar, pred čimer ni dolarja, je to takole:
$.uniqArray = function(a) {
return $.grep(a, function(item, pos) {
return $.inArray(item, a) === pos;
});
}
Klicanje funkcij je v javascriptu drago, zato zgornje rešitve, čeprav so jedrnate, niso posebej učinkovite. Za največjo učinkovitost zamenjajte filter
z zanko in se znebite drugih funkcijskih klicev:
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;
}
Ta kos grde kode naredi enako kot zgornji izsek #3, vendar za red velikosti hitreje (od leta 2017 je le dvakrat hitrejši - ljudje iz jedra JS opravljajo odlično delo!)
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 ponuja objekt Set, s katerim so stvari veliko lažje:
function uniq(a) {
return Array.from(new Set(a));
}
ali
let uniq = a => [...new Set(a)];
Na enaki osnovi je mogoče zgraditi "leno", na generatorjih temelječo različico uniqa
:
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
Hitro in umazano z uporabo jQueryja:
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: Odstranjevanje podvojevanj z uporabo predmeta kot množice
Vedno ga lahko poskusite vstaviti v objekt in nato iterirati po njegovih ključih:
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: Odstranjevanje podvajanj s sledenjem že videnim vrednostim (varno za vrstni red)
Ali pa za različico, ki je varna za vrstni red, uporabite objekt za shranjevanje vseh že videnih vrednosti in pred dodajanjem v polje preverite vrednosti glede na objekt.
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: uporaba nove podatkovne strukture Set (varna za vrstni red)
ECMAScript 6 dodaja novo podatkovno strukturo Set
, ki omogoča shranjevanje vrednosti katere koli vrste. Set.values
vrne elemente v vrstnem redu vstavljanja.
function remove_duplicates_es6(arr) {
let s = new Set(arr);
let it = s.values();
return Array.from(it);
}
Primer uporabe:
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"]