【亚洲必赢官网】js数组和字符串去重新二种艺术,也谈JavaScript数组去重

也谈JavaScript数组去重

2017/01/07 · JavaScript
· 数组

初稿出处:
TooBug(@TooBug)   

JavaScript的数组去重是一个老生常谈的话题了。随便搜一搜就能找到卓殊多分裂版本的解法。

昨天在新浪上看到一篇文章,也写数组去重,主要强调的法子是将运用数组元素当作对象key来去重。我在微博转载了“用对象key去重不是个好措施…”然后小编问怎么样才是援引的主意。

细想一下,那样一个类似简单的急需,假如要到位完备,涉及的知识和急需注意的地方实在不少,于是诞生此文。

js数组去重新三种格局

使用

1.万一不敬爱任何方法,初级写法应该是:

概念再次(相等)

要去重,首先得定义,什么叫作“重复”,即现实到代码而言,三个数据在怎么着情状下得以算是卓绝的。那并不是一个很简单的题材。

对于原始值而言,大家很简单想到11是相等的,'1''1'也是相等的。那么,1'1'是优异的么?

比方这几个标题还好说,只要回答“是”或者“不是”即可。那么上边这几个处境就没那么简单了。

率先种:也是最笨的吧。

复制代码 代码如下:

function unique(arr){
    var res=[];
    for(var i=0,len=arr.length;i<len;i++){
        var obj = arr[i];
        for(var j=0,jlen = res.length;j<jlen;j++){
            if(res[j]===obj) break;            
        }
        if(jlen===j)res.push(obj);
    }
    return res;
}
var arr=[1,1,'2','1',3,4]
arr = unique(arr);
console.log(arr);

NaN

初看NaN时,很不难把它正是和nullundefined同样的单身数据类型。但实际,它是数字类型。

JavaScript

// number console.log(typeof NaN);

1
2
// number
console.log(typeof NaN);

基于标准,相比较运算中一旦有一个值为NaN,则比较结实为false,所以会有上边这么些看起来略蛋疼的定论:

JavaScript

// 全都是false 0 < NaN; 0 > NaN; 0 == NaN; 0 === NaN;

1
2
3
4
5
// 全都是false
0 < NaN;
0 > NaN;
0 == NaN;
0 === NaN;

以最终一个表明式0 === NaN为例,在规范中有明确规定():

  1. If Type(x) is Number, then
    a. If x is NaN, return false.
    b. If y is NaN, return false.
    c. If x is the same Number value as y, return true.
    d. If x is +0 and y is −0, return true.
    e. If x is −0 and y is +0, return true.
    f. Return false.

那意味任何关联到NaN的状态都不可以不难地选择比较运算来判定是不是等于。比较不错的措施只好是利用isNaN()

JavaScript

var a = NaN; var b = NaN;   // true console.log(isNaN(a) && isNaN(b));

1
2
3
4
5
var a = NaN;
var b = NaN;
 
// true
console.log(isNaN(a) && isNaN(b));

Array.prototype.unique1 = function () {
var r = new Array();
label:for(var i = 0, n = this.length; i < n; i++) {
for(var x = 0, y = r.length; x < y; x++) {
if(r[x] == this[i]) {
continue label;
}
}
r[r.length] = this[i];
}
return r;
}
其次种:那几个正则天书一样。

<script language=javascript>
var isNumeric = function(x) {
   // returns true if x is numeric and false if it is not.
   var RegExp = /^(-)?(\d*)(\.?)(\d*)$/;
   return String(x).match(RegExp);
}
var myArray = [1,’two’,3,’four’,5,’six’,7,’eight’,9,’ten’];
var oddArray=myArray.filter(isNumeric);  // outputs: 1,3,5,7,9
var oddArray=myArray.some(isNumeric);  // outputs: true
var oddArray=myArray.every(isNumeric);  // outputs: false
var printArray =function(x, idx){
   document.writeln(‘[‘+idx+’] = ‘+x);
}
myArray.forEach(printArray);// outputs: [0] = 1 [1] = two [2] = 3
[3] = four [4] = 5
myArray.remove(9);
document.writeln(myArray);

 

原始值和包装对象

看完NaN是还是不是头都大了。好了,大家来轻松一下,看一看原始值和打包对象这一对恋人。

只要您商量过'a'.trim()如此那般的代码的话,不亮堂是否暴发过这么的问号:'a'鲜明是一个原始值(字符串),它干吗可以直接调用.trim()艺术吧?当然,很可能您曾经驾驭答案:因为JS在进行那样的代码的时候会对原始值做四遍包装,让'a'改为一个字符串对象,然后实施这几个目的的办法,执行完之后再把那几个包裹对象脱掉。可以用上边的代码来通晓:

JavaScript

// ‘a’.trim(); var tmp = new String(‘a’); tmp.trim();

1
2
3
// ‘a’.trim();
var tmp = new String(‘a’);
tmp.trim();

那段代码只是辅助我们知晓的。但包装对象那么些定义在JS中却是真实存在的。

JavaScript

var a = new String(‘a’); var b = ‘b’;

1
2
var a = new String(‘a’);
var b = ‘b’;

a即是一个打包对象,它和b同一,代表一个字符串。它们都可以运用字符串的各样法子(比如trim()),也可以涉足字符串运算(+号连接等)。

但她俩有一个重中之重的界别:类型不相同!

JavaScript

typeof a; // object typeof b; // string

1
2
typeof a; // object
typeof b; // string

在做字符串相比的时候,类型的两样会造成结果有局地出人意料:

JavaScript

var a1 = ‘a’; var a2 = new String(‘a’); var a3 = new String(‘a’); a1 ==
a2; // true a1 == a3; // true a2 == a3; // true a1 === a2; // false a1
=== a3; // false a2 === a3; // false

1
2
3
4
5
6
7
8
9
var a1 = ‘a’;
var a2 = new String(‘a’);
var a3 = new String(‘a’);
a1 == a2; // true
a1 == a3; // true
a2 == a3; // true
a1 === a2; // false
a1 === a3; // false
a2 === a3; // false

一致是代表字符串a的变量,在运用严厉比较时居然不是至极的,在直觉上那是一件相比难接受的事务,在各类成本情形下,也非凡不难忽略这几个细节。

Array.prototype.unique2 = function () {
return
this.sort().join(“,,”).replace(/(,|^)([^,]+)(,,\2)+(,|$)/g,”$1$2$4″).replace(/,,+/g,”,”).replace(/,$/,””).split(“,”);
}
其三种:使用对象的【hasOwnProperty】方法

复制代码 代码如下:

2.只要不考虑兼容性,会用ES5里面数组的indexOf()方法。

目标和对象

在关乎比较的时候,还会遇上对象。具体而言,大概可以分成两种情景:纯对象、实例对象、其余类其余靶子。

纯对象

纯对象(plain
object)具体指什么并不是可怜明显,为减弱不要求的冲突,下文中行使纯对象指代由字面量生成的、成员中不含函数和日期、正则表明式等档次的目标。

若是一向拿多少个对象进行比较,不管是==还是===,毫无疑问都是不等于的。可是在实质上拔取时,那样的平整是或不是必然满足大家的必要?举个例证,我们的应用中有八个布局项:

JavaScript

// 原来有七个属性 // var prop1 = 1; // var prop2 = 2; //
重构代码时七个属性被停放同一个目的中 var config = { prop1: 1, prop2: 2
};

1
2
3
4
5
6
7
8
// 原来有两个属性
// var prop1 = 1;
// var prop2 = 2;
// 重构代码时两个属性被放到同一个对象中
var config = {
    prop1: 1,
    prop2: 2
};

借使在某些场景下,咱们要求相比五次运行的配备项是或不是同样。在重构前,大家独家相比五回运行的prop1prop2即可。而在重构后,大家兴许必要相比config目标所代表的安排项是还是不是相同。在那样的风貌下,直接用==或者===来相比较对象,得到的并不是大家愿意的结果。

在那样的情景下,我们兴许要求自定义一些主意来拍卖对象的比较。常见的可能是经过JSON.stringify()对目的举行连串化之后再比较字符串,当然这么些进程不要全盘可相信,只是一个思路。

若果您觉得这几个现象是捏造的话,可以再回看一下断言库,同样是根据对象成员,判断结果是或不是和预期相符。

实例对象

实例对象紧要指通过构造函数(类)生成的靶子。那样的靶子和纯对象一样,直接相比都是不相同的,但也会遇见须求看清是还是不是是同一对象的气象。一般而言,因为那种对象有比较复杂的内部结构(甚至有部分数额在原型上),无法直接从表面比较是还是不是等于。比较可信的论断方式是由构造函数(类)来提供静态方法或者实例方法来判断是不是等于。

JavaScript

var a = Klass(); var b = Klass(); Klass.isEqual(a, b);

1
2
3
var a = Klass();
var b = Klass();
Klass.isEqual(a, b);

其余对象

其它对象主要指数组、日期、正则表明式等那类在Object基本功上派生出来的目标。那类对象各有各的特殊性,一般必要基于气象来布局判断格局,决定四个对象是或不是等于。

譬如,日期对象,可能须要经过Date.prototype.getTime()艺术得到时间戳来判断是不是意味着无异时刻。正则表达式可能须要通过toString()主意取拿到原始字面量来判定是不是是相同的正则表明式。

Array.prototype.unique3 = function() {
var temp = {}, len = this.length;
for(var i=0; i < len; i++) {
var tmp = this[i];
if(!temp.hasOwnProperty(tmp)) {
temp[this[i]] = “my god”;
}
}

if (!Array.prototype.every)
{
  Array.prototype.every = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != “function”)
      throw new TypeError();

function unique(arr){
    var res =[];
    for(var i=0,len=arr.length;i<len;i++){
        var obj = arr[i];
        if(res.indexOf(obj)===-1) res.push(obj);
    }
    return res;
}
var arr=[1,1,'2','1',3,4]
arr = unique(arr);
console.log(arr);// arr=[1,'2','1',3,4]

==和===

在一些稿子中,看到某部分数组去重的点子,在认清元素是或不是等于时,使用的是==比较运算符。众所周知,那一个运算符在可比前会先查看元素类型,当类型不平等时会做隐式类型转换。那实质上是一种极度不审慎的做法。因为不能区分在做隐匿类型转换后值一样的因素,例如0''falsenullundefined等。

再就是,还有可能出现局地不得不黑人问号的结果,例如:

JavaScript

[] == ![]; //true

1
[] == ![]; //true

len = 0;
var tempArr=[];
for(var i in temp) {
tempArr[len++] = i;
}
return tempArr;
}
第四种:先排序,前项比后项。那一个办法挺简单的,但也实用

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this &&
          !fun.call(thisp, this[i], i, this))
        return false;
    }

3.运用ES5数组里的filter过滤:

Array.prototype.indexOf()

在一部分版本的去重中,用到了Array.prototype.indexOf()方法:

JavaScript

function unique(arr) { return arr.filter(function(item, index){ //
indexOf重返第二个索引值, // 如若当前索引不是率先个目录,表达是再次值
return arr.indexOf(item) === index; }); }

1
2
3
4
5
6
7
function unique(arr) {
    return arr.filter(function(item, index){
        // indexOf返回第一个索引值,
        // 如果当前索引不是第一个索引,说明是重复值
        return arr.indexOf(item) === index;
    });
}

JavaScript

function unique(arr) { var ret = []; arr.forEach(function(item){
if(ret.indexOf(item) === -1){ ret.push(item); } }); return ret; }

1
2
3
4
5
6
7
8
9
function unique(arr) {
    var ret = [];
    arr.forEach(function(item){
        if(ret.indexOf(item) === -1){
            ret.push(item);
        }
    });
    return ret;
}

既然=====在要素相等的相比较中是有巨大差距的,那么indexOf的状态又怎么呢?大部分的文章都并未提及那一点,于是只好求助规范。通过正规(),大家了然了indexOf()应用的是严苛比较,也就是===

重新强调:依据前文所述,===无法处理NaN的相等性判断。

Array.prototype.unique4 = function () {
var temp = new Array();
this.sort();
for(i = 0; i < this.length; i++) {
if( this[i] == this[i+1]) {
continue;
}
temp[temp.length]=this[i];
}
return temp;

    return true;
  };
}
if (!Array.prototype.filter)
{
  Array.prototype.filter = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != “function”)
      throw new TypeError();

function unique(a) {

  var res = a.filter(function(item, index, array) {
    return array.indexOf(item) === index;
  });

  return res;
}


var a = [1, 1, '1', '2', 1];
var ans = unique(a);
console.log(ans); 

Array.prototype.includes()

Array.prototype.includes()是ES2016中新增的艺术,用于判断数组中是还是不是含有某个元素,所以地方运用indexOf()形式的第一个本子可以改写成如下版本:

JavaScript

function unique(arr) { var ret = []; arr.forEach(function(item){
if(!ret.includes(item)){ ret.push(item); } }); return ret; }

1
2
3
4
5
6
7
8
9
function unique(arr) {
    var ret = [];
    arr.forEach(function(item){
        if(!ret.includes(item)){
            ret.push(item);
        }
    });
    return ret;
}

那么,你猜猜,includes()又是用怎么样点子来相比较的呢?如果想当然的话,会认为肯定跟indexOf()如出一辙喽。可是,程序员的社会风气里最怕想当然。翻一翻规范,发现它实际是行使的另一种比较艺术,叫作“SameValueZero”比较()。

  1. If Type(x) is different from Type(y), return false.
  2. If Type(x) is Number, then
    【亚洲必赢官网】js数组和字符串去重新二种艺术,也谈JavaScript数组去重。a. If x is NaN and y is NaN, return true.
    b. If x is +0 and y is -0, return true.
    c. If x is -0 and y is +0, return true.
    d. If x is the same Number value as y, return true.
    e. Return false.
  3. Return SameValueNonNumber(x, y).

注意2.a,如果xy都是NaN,则返回true!也就是includes()是可以正确判断是或不是带有了NaN的。大家写一段代码验证一下:

JavaScript

var arr = [1, 2, NaN]; arr.indexOf(NaN); // -1 arr.includes(NaN); //
true

1
2
3
var arr = [1, 2, NaN];
arr.indexOf(NaN); // -1
arr.includes(NaN); // true

可以见见indexOf()includes()对待NaN的行为是完全不雷同的。

}

    var res = new Array();
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
      {
        var val = this[i]; // in case fun mutates this
        if (fun.call(thisp, val, i, this))
          res.push(val);
      }
    }

 

有的方案

从地点的一大段文字中,大家可以见见,要认清多个要素是不是等于(重复)并不是一件简单的作业。在打听了那些背景后,大家来看有些面前没有提到到的去重方案。

下边是先前常常用的,作用也很好。有点想hash表的痛感。

    return res;
  };
}
if (!Array.prototype.forEach)
{
  Array.prototype.forEach = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != “function”)
      throw new TypeError();

方法二

遍历

双重遍历是最简单想到的去重方案:

JavaScript

function unique(arr) { var ret = []; var len = arr.length; var
isRepeat; for(var i=0; i<len; i++) { isRepeat = false; for(var j=i+1;
j<len; j++) { if(arr[i] === arr[j]){ isRepeat = true; break; } }
if(!isRepeat){ ret.push(arr[i]); } } return ret; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function unique(arr) {
    var ret = [];
    var len = arr.length;
    var isRepeat;
    for(var i=0; i<len; i++) {
        isRepeat = false;
        for(var j=i+1; j<len; j++) {
            if(arr[i] === arr[j]){
                isRepeat = true;
                break;
            }
        }
        if(!isRepeat){
            ret.push(arr[i]);
        }
    }
    return ret;
}

再一次遍历还有一个优化版本,不过原理和复杂度几乎统统平等:

JavaScript

function unique(arr) { var ret = []; var len = arr.length; for(var
i=0; i<len; i++){ for(var j=i+1; j<len; j++){ if(arr[i] ===
arr[j]){ j = ++i; } } ret.push(arr[i]); } return ret; }

1
2
3
4
5
6
7
8
9
10
11
12
13
function unique(arr) {
    var ret = [];
    var len = arr.length;
    for(var i=0; i<len; i++){
        for(var j=i+1; j<len; j++){
            if(arr[i] === arr[j]){
                j = ++i;
            }
        }
        ret.push(arr[i]);
    }
    return ret;
}

那种方案没什么大难题,用于去重的可比部分也是祥和编辑完毕(arr[i] === arr[j]),所以相等性可以协调针对上文说到的各类场所再说特殊处理。唯一相比受诟病的是利用了再度循环,时间复杂度相比较高,性能一般。

Array.prototype.unique5 = function() {
var res = [], hash = {};
for(var i=0, elem; (elem = this[i]) != null; i++) {
if (!hash[elem])
{
res.push(elem);
hash[elem] = true;
}
}
return res;
}

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
        fun.call(thisp, this[i], i, this);
    }
  };
}
if (!Array.prototype.map)
{
  Array.prototype.map = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != “function”)
      throw new TypeError();

 

应用对象key来去重

JavaScript

function unique(arr) { var ret = []; var len = arr.length; var tmp =
{}; for(var i=0; i<len; i++){ if(!tmp[arr[i]]){ tmp[arr[i]] =
1; ret.push(arr[i]); } } return ret; }

1
2
3
4
5
6
7
8
9
10
11
12
function unique(arr) {
    var ret = [];
    var len = arr.length;
    var tmp = {};
    for(var i=0; i<len; i++){
        if(!tmp[arr[i]]){
            tmp[arr[i]] = 1;
            ret.push(arr[i]);
        }
    }
    return ret;
}

那种情势是使用了对象(tmp)的key不得以另行的风味来展开去重。但鉴于目的key只可以为字符串,由此那种去重方法有诸多局限性:

  1. 没辙区分隐式类型转换成字符串后一致的值,比如1'1'
  2. 没辙处理复杂数据类型,比如对象(因为对象作为key会变成[object Object]
  3. 独特数据,比如'__proto__'会挂掉,因为tmp对象的__proto__特性无法被重写

对此第一点,有人提出可以为对象的key增添一个档次,或者将品种放到对象的value中来缓解:

JavaScript

function unique(arr) { var ret = []; var len = arr.length; var tmp =
{}; var tmpKey; for(var i=0; i<len; i++){ tmpKey = typeof arr[i] +
arr[i]; if(!tmp[tmpKey]){ tmp[tmpKey] = 1; ret.push(arr[i]); } }
return ret; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function unique(arr) {
    var ret = [];
    var len = arr.length;
    var tmp = {};
    var tmpKey;
    for(var i=0; i<len; i++){
        tmpKey = typeof arr[i] + arr[i];
        if(!tmp[tmpKey]){
            tmp[tmpKey] = 1;
            ret.push(arr[i]);
        }
    }
    return ret;
}

该方案也同时解决第二个难点。

而第一个难题,若是像上文所说,在同意对目标举办自定义的相比规则,也得以将目的系列化之后作为key来利用。那里为不难起见,使用JSON.stringify()开展序列化。

JavaScript

function unique(arr) { var ret = []; var len = arr.length; var tmp =
{}; var tmpKey; for(var i=0; i<len; i++){ tmpKey = typeof arr[i] +
JSON.stringify(arr[i]); if(!tmp[tmpKey]){ tmp[tmpKey] = 1;
ret.push(arr[i]); } } return ret; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function unique(arr) {
    var ret = [];
    var len = arr.length;
    var tmp = {};
    var tmpKey;
    for(var i=0; i<len; i++){
        tmpKey = typeof arr[i] + JSON.stringify(arr[i]);
        if(!tmp[tmpKey]){
            tmp[tmpKey] = 1;
            ret.push(arr[i]);
        }
    }
    return ret;
}

还是可以有种更不难明了的写法:

    var res = new Array(len);
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
        res[i] = fun.call(thisp, this[i], i, this);
    }

法一是将原数组中的元素和结果数组中的元素一一相比,大家得以换个思路,将原数组中另行元素的最终一个要素放入结果数组中。

Map Key

可以看来,使用对象key来拍卖数组去重的标题,其实是一件相比费心的政工,处理不好很简单导致结果不正确。而这么些标题的根本原因就是因为key在行使时有限制。

那就是说,能无法有一种key使用没有范围的目的啊?答案是——真的有!那就是ES2015中的Map

Map是一种新的数据类型,可以把它想象成key类型没有界定的对象。别的,它的存取使用单独的get()set()接口。

JavaScript

var tmp = new Map(); tmp.set(1, 1); tmp.get(1); // 1 tmp.set(‘2’, 2);
tmp.get(‘2’); // 2 tmp.set(true, 3); tmp.get(true); // 3
tmp.set(undefined, 4); tmp.get(undefined); // 4 tmp.set(NaN, 5);
tmp.get(NaN); // 5 var arr = [], obj = {}; tmp.set(arr, 6);
tmp.get(arr); // 6 tmp.set(obj, 7); tmp.get(obj); // 7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var tmp = new Map();
tmp.set(1, 1);
tmp.get(1); // 1
tmp.set(‘2’, 2);
tmp.get(‘2’); // 2
tmp.set(true, 3);
tmp.get(true); // 3
tmp.set(undefined, 4);
tmp.get(undefined); // 4
tmp.set(NaN, 5);
tmp.get(NaN); // 5
var arr = [], obj = {};
tmp.set(arr, 6);
tmp.get(arr); // 6
tmp.set(obj, 7);
tmp.get(obj); // 7

是因为Map使用单独的接口来存取数据,所以并非操心key会和停放属性重名(如上文提到的__proto__)。使用Map改写一下大家的去重方法:

JavaScript

function unique(arr) { var ret = []; var len = arr.length; var tmp =
new Map(); for(var i=0; i<len; i++){ if(!tmp.get(arr[i])){
tmp.set(arr[i], 1); ret.push(arr[i]); } } return ret; }

1
2
3
4
5
6
7
8
9
10
11
12
function unique(arr) {
    var ret = [];
    var len = arr.length;
    var tmp = new Map();
    for(var i=0; i<len; i++){
        if(!tmp.get(arr[i])){
            tmp.set(arr[i], 1);
            ret.push(arr[i]);
        }
    }
    return ret;
}

亚洲必赢官网,/*使用了聚众的合计,有序不另行*/
function removeDuplicates(arr) {

    return res;
  };
}
if (!Array.prototype.some)
{
  Array.prototype.some = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != “function”)
      throw new TypeError();

 

Set

既然如此都用到了ES2015,数组那件工作无法再简单一点么?当然可以。

除了Map以外,ES2015还引入了一种叫作Set的数据类型。顾名思义,Set即便聚集的趣味,它不允许再一次元素出现,这或多或少和数学中对聚集的定义依旧比较像的。

JavaScript

var s = new Set(); s.add(1); s.add(‘1’); s.add(null); s.add(undefined);
s.add(NaN); s.add(true); s.add([]); s.add({});

1
2
3
4
5
6
7
8
9
var s = new Set();
s.add(1);
s.add(‘1’);
s.add(null);
s.add(undefined);
s.add(NaN);
s.add(true);
s.add([]);
s.add({});

一旦您再一次添加同一个要素的话,Set中只会设有一个。包涵NaN也是如此。于是大家想到,这么好的特性,即使能和数组互相转换,不就足以去重了吧?

JavaScript

function unique(arr){ var set = new Set(arr); return Array.from(set); }

1
2
3
4
function unique(arr){
    var set = new Set(arr);
    return Array.from(set);
}

我们谈谈了这么久的作业,居然两行代码搞定了,几乎不可名状。

不过,不要只顾着兴奋了。有一句话是那样说的“不要因为走得太远而忘了为什么出发”。大家为什么要为数组去重呢?因为大家想赢得不重复的因素列表。而既然已经有Set了,我们为什么还要舍近求远,使用数组呢?是否在需求去重的境况下,直接行使Set就缓解难题了?那些问题值得思考。

    var temp = {}, r = [];

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this &&
          fun.call(thisp, this[i], i, this))
        return true;
    }

function unique(a) {

  var res = [];

 

  for (var i = 0, len = a.length; i < len; i++) {

    for (var j = i + 1; j < len; j++) {

      // 这一步极度巧妙

      // 即便发现同样元素

      // 则 i 自增长入下一个循环往复比较

      if (a[i] === a[j])

        j = ++i;

    }

 

    res.push(a[i]);

  }

 

  return res;

}

 

 

var a = [1, 1, ‘1’, ‘2’, 1];

var ans = unique(a);

console.log(ans); // => [“1”, “2”, 1]

小结

最后,用一个测试用例总括一下文中现身的各个去重方法:

JavaScript

var arr =
[1,1,’1′,’1′,0,0,’0′,’0′,undefined,undefined,null,null,NaN,NaN,{},{},[],[],/a/,/a/]
console.log(unique(arr));

1
2
var arr = [1,1,’1′,’1′,0,0,’0′,’0′,undefined,undefined,null,null,NaN,NaN,{},{},[],[],/a/,/a/]
console.log(unique(arr));

测试中没有定义对象的相比较艺术,因而默许处境下,对象不去重是正确的结果,去重是不得法的结果。

方法 结果 说明
indexOf#1 NaN被去掉
indexOf#2 NaN重复
includes 正确
双重循环#1 NaN重复
双重循环#2 NaN重复
对象#1 字符串和数字无法区分,对象、数组、正则表达式被去重
对象#2 对象、数组、正则表达式被去重
对象#3 对象、数组被去重,正则表达式被消失 JSON.stringify(/a/)结果为{},和空对象一样
Map 正确
Set 正确

末段的最终:任何脱离场景谈技术都是妄谈,本文也一样。去重这道题,没有正确答案,请依照气象选拔万分的去重方法。

1 赞 3 收藏
评论

亚洲必赢官网 1

    for (var i in arr)

    return false;
  };
}
Array.prototype.sortNum = function() {
   return this.sort( function (a,b) { return a-b; } );
}
<!–
var tmp = [5,9,12,18,56,1,10,42,’blue’,30,
7,97,53,33,30,35,27,30,’35’,’Ball’, ‘bubble’];
var thirty=tmp.find(30);             // Returns 9, 14, 17
var thirtyfive=tmp.find(’35’);       // Returns 18
var thirtyfive=tmp.find(35);         // Returns 15
var haveBlue=tmp.find(‘blue’);       // Returns 8
var notFound=tmp.find(‘not there!’); // Returns false
var regexp1=tmp.find(/^b/);          // returns 8,20    (first letter
starts with b)
var regexp1=tmp.find(/^b/i);         // returns 8,19,20 (same as above
but ignore case)
–>
Array.prototype.find = function(searchStr) {
  var returnArray = false;
  for (i=0; i<this.length; i++) {
    if (typeof(searchStr) == ‘function’) {
      if (searchStr.test(this[i])) {
        if (!returnArray) { returnArray = [] }
        returnArray.push(i);
      }
    } else {
      if (this[i]===searchStr) {
        if (!returnArray) { returnArray = [] }
        returnArray.push(i);
      }
    }
  }
  return returnArray;
}
//随机改变数组的排序
Array.prototype.shuffle = function (){  
    for(var rnd, tmp, i=this.length; i; rnd=parseInt(Math.random()*i),
tmp=this[–i], this[i]=this[rnd], this[rnd]=tmp); 
 return this;
}  
<!–var myArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
var yourArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
document.writeln(myArray.compare(yourArray)); // outputs: true;–>
Array.prototype.compare = function(testArr) {
    if (this.length != testArr.length) return false;
    for (var i = 0; i < testArr.length; i++) {
        if (this[i].compare) {
            if (!this[i].compare(testArr[i])) return false;
        }
        if (this[i] !== testArr[i]) return false;
    }
    return true;
}
//去掉数组中再一次的值var a = new Array(“5″,”7″,”7”); a.unique();
Array.prototype.unique = function() {
 var data = this || [];
    var a = {}; //声美赞臣(Meadjohnson)个目的,javascript的对象足以当哈希表用
    for (var i = 0; i < data.length; i++) {
        a[data[i]] = true; 
//设置标记,把数组的值当下标,那样就足以去掉重复的值
    }
    data.length = 0;

 

        temp[arr[i]] = true;

    for (var i in a) { //遍历对象,把已标记的还原成数组
        this[data.length] = i;
    }
    return data;
}

固然如此复杂度依旧 O(n^2),可是可以看到结果分裂,1
面世在了数组最前面,因为结果数组取的是因素最终一遍面世的职务。

    for (var k in temp)

Array.prototype.addAll = function($array)
{
 if($array == null || $array.length == 0)
  return;

 

        r.push(k);

 for(var $i=0; $i<$array.length; $i++)
  this.push($array[$i]);
}

方法三(sort)

    return r;

Array.prototype.contains = function($value)
{
 for(var $i=0; $i<this.length; $i++)
 {
  var $element = this[$i];
  if($element == $value)
   return true;
 }

 

}

 return false;
}

假使笔试面试时只答出了地点那样 O(n^2)
的方案,可能还不可能使面试官满意,上面就来说二种进阶方案。

看起来挺好不难的吗

Array.prototype.indexOf = function($value)
{
 for(var $i=0; $i<this.length; $i++)
 {
  if(this[$i] == $value)
   return $i;
 }

 

经过协调表明过了,代码如下:

 return -1;
}
if (!Array.prototype.lastIndexOf)
{
  Array.prototype.lastIndexOf = function(elt /*, from*/)
  {
    var len = this.length;

将数组用 sort
排序后,理论上亦然的元素会被放在附近的任务,那么比较前后地点的因素就可以了。

 1 function removeDuplicates(arr) {
 2 
 3     var temp = {}, r = [];
 4 
 5     for (var i in arr)
 6 
 7         temp[arr[i]] = true;
 8 
 9     for (var k in temp)
10 
11         r.push(k);
12 
13     return r;
14 
15 }
16 
17 //用法
18 
19 var fruits = ['apple', 'orange', 'peach', 'apple', 'strawberry', 'orange', 'strawberry', 'orange'];
20 
21 var uniquefruits = removeDuplicates(fruits);
22 alert(uniquefruits);

    var from = Number(arguments[1]);
    if (isNaN(from))
    {
      from = len – 1;
    }
    else
    {
      from = (from < 0)
           ? Math.ceil(from)
           : Math.floor(from);
      if (from < 0)
        from += len;
      else if (from >= len)
        from = len – 1;
    }

 

字符串去重方法:

    for (; from > -1; from–)
    {
      if (from in this &&
          this[from] === elt)
        return from;
    }
    return -1;
  };
}
Array.prototype.insertAt = function($value, $index)
{
 if($index < 0)
  this.unshift($value);
 else if($index >= this.length)
  this.push($value);
 else
  this.splice($index, 0, $value);
}
/**
* 按照数组的下标来删除元素
*/ 
Array.prototype.removeByIndex=function($n) {  
    if($n<0){ //要是n<0,则不开展任何操作。 
      return this; 
    }else{ 
        return this.slice(0,$n).concat(this.slice($n+1,this.length)); 
    } 
}
//依赖indexOf
Array.prototype.remove = function($value)
{
 var $index = this.indexOf($value);

function unique(a) {

  return a.concat().sort().filter(function(item, pos, ary) {

    return !pos || item != ary[pos – 1];

  });

}

 

 

var a = [1, 1, 3, 2, 1, 2, 4];

var ans = unique(a);

console.log(ans); // => [1, 2, 3, 4]

应用正则

 if($index != -1)
  this.splice($index, 1);
}

 

/**
* 字符串去重
* \r\n字符串分隔符
* $1分割后的字符串,$2字符串的目录
* 以分隔符将字符串分割,根据分割后的数组元素的个数举行巡回相比较
*/
    function strUnique(){
        var str = “abc, abcd, abc, abcde, abcd, abcde”;
        var ret = [];
        str.replace(/[^,]+/g, function($1, $2){
            (str.indexOf($1) == $2) && ret.push($1);
        });
        alert(ret);
        return ret;
    }

Array.prototype.removeAll = function()
{
 while(this.length > 0)
  this.pop();
}

然则难点又来了,1 和 “1” 会被排在一起,分裂的 Object
会被排在一起,因为它们 toString() 的结果一致,所以会并发这样的荒唐:

 1     function strUnique(){
 2         var str = "abc, abcd, abc, abcde, abcd, abcde";
 3         var ret = [];
 4         str.replace(/[^,]+/g, function($1, $2){
 5             (str.indexOf($1) == $2) && ret.push($1);
 6         });
 7         alert(ret);
 8         return ret;
 9     }
10     
11 strUnique();

Array.prototype.replace = function($oldValue, $newValue)
{
 for(var $i=0; $i<this.length; $i++)
 {
  if(this[$i] == $oldValue)
  {
   this[$i] = $newValue;
   return;
  }
 }
}

 

 

Array.prototype.swap = function($a, $b)
{
 if($a == $b)
  return;

var a = [1, 1, 3, 2, 1, 2, 4, ‘1’];

var ans = unique(a);

console.log(ans); // => [1, 2, 3, 4]

 var $tmp = this[$a];
 this[$a] = this[$b];
 this[$b] = $tmp;
}
Array.prototype.max = function() { 
 return Math.max.apply({}, this); 

Array.prototype.min = function() { 
 return Math.min.apply({}, this); 
}
Array.prototype.splice = function(start, delLen, item){
 var len =this.length;
 start = start<0?0:start>len?len:start?start:0;
 delLen=delLen<0?0:delLen>len?len:delLen?delLen:len; 

 

 var arr =[],res=[];
 var iarr=0,ires=0,i=0;

理所当然你一点一滴可以本着数组中或许出现的例外连串,来写那么些比较函数。不过那似乎有些麻烦。

 for(i=0;i<len;i++){
  if(i<start|| ires>=delLen) arr[iarr++]=this[i];
  else {
   res[ires++]=this[i];
   if(item&&ires==delLen){
    arr[iarr++]=item;
   }
  } 
 }
 if(item&&ires<delLen) arr[iarr]=item;

 

 for(var i=0;i<arr.length;i++){
  this[i]=arr[i];
 }
 this.length=arr.length;
 return res;
}
Array.prototype.shift = function(){ if(!this) return[];return
this.splice(0,1)[0];}

方法四 (object)

//分开添加,关键字shallow copy,如若遇上数组,复制数组中的元素
Array.prototype.concat = function(){
 var i=0;
 while(i<arguments.length){
  if(typeof arguments[i] === ‘object’&&typeof arguments[i].splice
===’function’ &&!arguments[i].propertyIsEnumerable(‘length’)){
  // NOT SHALLOW COPY BELOW
  // Array.prototype.concat.apply(this,arguments[i++]);
   var j=0;
   while(j<arguments[i].length) this.splice(this.length,0,arguments[i][j++]);
   i++;
  } else{
   this[this.length]=arguments[i++];
  }
 }
 return this;
}

 

Array.prototype.join = function(separator){
 var i=0,str=””;
 while(i<this.length) str+=this[i++]+separator;
 return str;
}

用 JavaScript 中的 Object 对象来作为哈希表,那也是几年前笔试时的解法,跟
sort 一样,可以去重完全由 Number 基本类型组成的数组。

Array.prototype.pop = function() { return
this.splice(this.length-1,1)[0];}

 

Array.prototype.push = function(){
 Array.prototype.splice.apply(this,
  [this.length,0].concat(Array.prototype.slice.apply(arguments)));
//这里没有直接处理参数,而是复制了眨眼之间间
 return this.length;
}
Array.prototype.reverse = function(){
 for(var i=0;i<this.length/2;i++){
  var temp = this[i];
  this[i]= this[this.length-1-i];
  this[this.length-1-i] = temp;
 }
 return this;
}
Array.prototype.slice = function(start, end){
 var len =this.length;
 start=start<0?start+=len:start?start:0;
 end =end<0?end+=len:end>len?len:end?end:len;

function unique(a) {

  var seen = {};

 

  return a.filter(function(item) {

    return seen.hasOwnProperty(item) ? false : (seen[item] = true);

  });

}

 

 

var a = [1, 1, 3, 2, 1, 2, 4];

var ans = unique(a);

console.log(ans); // => [1, 3, 2, 4]

 var i=start;
 var res = [];
 while(i<end){
  res.push(this[i++]);
 }
 return res; 
}
//arr.unshift(ele1,ele2,ele3….)
Array.prototype.unshift =function(){
 Array.prototype.splice.apply(this,[0,0].concat(Array.prototype.slice.apply(this,arguments)));
}

 

你或许感兴趣的小说:

  • 以JSON格局将JS中Array对象数组传至后台的法门
  • JavaScript
    判断判断某个对象是Object依旧一个Array
  • js中判断Object、Array、Function等援引类型对象是还是不是等于
  • JavaScript数组Array对象增加和删除元素方法统计
  • Javascript中判断变量是数组仍旧对象(array如故object)
  • javascript
    Array对象使用小结
  • JScript内置对象Array中元素的删减方法
  • JavaScript中Array
    对象相关的几个主意
  • JavaScript中的Array对象使用验证
  • javascript中绘影绘声的Array对象详解

抑或和格局三均等的标题,因为 Object 的 key 值都是 String 类型,所以对于
1 和 “1” 无法分别,大家可以稍微立异下,将项目也存入 key 中。

 

function unique(a) {

  var ret = [];

  var hash = {};

 

  for (var i = 0, len = a.length; i < len; i++) {

    var item = a[i];

 

    var key = typeof(item) + item;

 

    if (hash[key] !== 1) {

      ret.push(item);

      hash[key] = 1;

    }

  }

 

  return ret;

}

 

 

var a = [1, 1, 3, 2, ‘4’, 1, 2, 4, ‘1’];

var ans = unique(a);

console.log(ans); // => [1, 3, 2, “4”, 4, “1”]

 

固然缓解了厌烦的 1 和 “1” 的难题,不过还有其余难点!

 

var a = [{name: “hanzichi”}, {age: 30}, new String(1), new
Number(1)];

var ans = unique(a);

console.log(ans); // => [Object, String]

 

唯独只要数组元素全体是基础项目标 Number 值,键值对法应该是最神速的!

 

方法五 (ES6)

 

ES6 布置了 Set 以及 Array.from
方法,太强大了!若是浏览器辅助,完全可以这么:

 

function unique(a) {

  return Array.from(new Set(a));

}

 

var a = [{name: “hanzichi”}, {age: 30}, new String(1), new
Number(1)];

var ans = unique(a);

console.log(ans); // => [Object, Object, String, Number]

 

_.unique

 

末段来探望 underscore 对此的已毕格局,underscore 将此封装到了 _.unique
方法中,调用格局为 _.unique(array, [isSorted],
[iteratee])。其中第二个参数是必须的,是需求去重的数组,第三个参数可选,假设数组有序,则足以流传布尔值
true,第二个参数可选,假使须要对数组迭代的结果去重,则足以流传一个迭代函数。而数组元素去重是根据=== 运算符的。

 

事实上很简短,underscore 中的落成情势和地点的法子一形似。

 

俺们来看它的为主代码:

 

for (var i = 0, length = getLength(array); i  length; i++) {

  var value = array[i],

      // 假诺指定了迭代函数

      // 则对数组每一个因素进行迭代

      computed = iteratee ? iteratee(value, i, array) : value;

 

  // 如若是有序数组,则当前因素只需跟上一个元素比较即可

  // 用 seen 变量保存上一个因素

  if (isSorted) {

    // 如果 i === 0,则直接 push

    // 否则比较当前元素是还是不是和前一个元素相等

    if (!i || seen !== computed) result.push(value);

    // seen 保存当前元素,供下五次比较

    seen = computed;

  } else if (iteratee) {

    // 如果 seen[] 中没有 computed 这些元素值

    if (!_.contains(seen, computed)) {

      seen.push(computed);

      result.push(value);

    }

  } else if (!_.contains(result, value)) {  

    // 如若不用经过迭代函数统计,也就无须 seen[] 变量了

    result.push(value);

  }

}

 

外界的巡回遍历数组元素,对于每个元素,若是数组有序,则和前一个要素比较,即使一致,则早就面世过,不进入到结果数组中,否则则参预。而假诺有迭代函数,则总结传入迭代函数后的值,对值去重,调用
.contains 方法,而该办法的焦点就是调用 .indexOf
方法,和大家地点说的点子一异曲同工。

 

关于 _.unique 方法的详实代码,可以参考

 

网站地图xml地图