8wDlpd.png
8wDFp9.png
8wDEOx.png
8wDMfH.png
8wDKte.png

从 JS 数组中删除重复的值

HizaCrenata 2月前

302 0

我有一个非常简单的 JavaScript 数组,其中可能包含或不包含重复项。var names = [\'Mike\',\'Matt\',\'Nancy\',\'Adam\',\'Jenny\',\'Nancy\',&...

我有一个非常简单的 JavaScript 数组,它可能包含或不包含重复项。

var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];

我需要删除重复项并将唯一值放入新数组中。

我可以指出我尝试过的所有代码,但我认为它们毫无用处,因为它们不起作用。我也接受 jQuery 解决方案。

类似问题:

  • 获取数组中的所有非唯一值(即:重复/多次出现)
帖子版权声明 1、本帖标题:从 JS 数组中删除重复的值
    本站网址:http://xjnalaquan.com/
2、本网站的资源部分来源于网络,如有侵权,请联系站长进行删除处理。
3、会员发帖仅代表会员个人观点,并不代表本站赞同其观点和对其真实性负责。
4、本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
5、站长邮箱:yeweds@126.com 除非注明,本帖由HizaCrenata在本站《arrays》版块原创发布, 转载请注明出处!
最新回复 (0)
  • 使用 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);
    });
    
  • 由于一位信誉良好的人将其恢复为原始的 inArray 解决方案,我将再次提及:该解决方案是 O(n^2),因此效率低下。

  • 我真的希望在 2020 年我们可以开始贬低 jQuery 和其他更过时的答案......这里开始显示出一些年龄......

  • 我同意@NickSteele的观点,但我发现如果你看的是投票而不是被接受的答案,随着时间的推移,这种情况确实会自然发生。随着旧的弃用答案被否决,最佳答案将趋向顶部

  • 让 uniqueNames = names.filter((item, pos ,self) => self.indexOf(item) == pos);

  • 如果您使用的是 jquery,则可以使用 $.unique,尽管它也会对结果中的项目进行排序。最佳答案如下(从数组创建一个集合),这个答案效率低下且过时。

  • 长话短说

    使用 Set 构造函数和 扩展语法 :

    uniq = [...new Set(array)];
    

    (请注意,var uniq 将是一个数组... new Set() 将其变成一个集合,但 [...] 将其再次变成一个数组)


    “聪明”但天真的方式

    uniqueArray = a.filter(function(item, pos) {
        return a.indexOf(item) == pos;
    })
    

    基本上,我们遍历数组,并针对每个元素检查该元素在数组中的第一个位置是否等于当前位置。显然,对于重复元素,这两个位置是不同的。

    使用过滤器回调的第三个参数(“this 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);
        });
    }
    

    通常的做法是这样的。其思路是将每个元素放入哈希表中,然后立即检查其是否存在。这为我们提供了线性时间,但至少有两个缺点:

    • 由于 JavaScript 中的哈希键只能是字符串或符号,因此此代码不区分数字和“数字字符串”。也就是说, uniq([1,"1"]) 将只返回 [1]
    • 出于同样的原因,所有对象将被视为相等: uniq([{foo:1},{foo:2}]) 将返回 [{foo:1}] .

    也就是说,如果您的数组仅包含基元并且您不关心类型(例如它始终是数字),那么此解决方案是最佳的。

    两个世界的最佳结合

    通用解决方案结合了这两种方法:它使用哈希查找来查找原语,使用线性搜索来查找对象。

    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);
        });
    }
    

    排序|独特的

    另一个选择是先对数组进行排序,然后删除每个与前一个元素相等的元素:

    function uniq(a) {
        return a.sort().filter(function(item, pos, ary) {
            return !pos || item != ary[pos - 1];
        });
    }
    

    再次强调,这不适用于对象(因为所有对象对于 都是相等的 sort 从上面 sort 删除即可

    独一无二的...

    有时,需要根据除相等性之外的某些标准来唯一化列表,例如,过滤掉不同但共享某些属性的对象。这可以通过传递回调来优雅地完成。此 \'key\'​​ 回调应用于每个元素,并且删除具有相等 \'keys\' 的元素。由于 key 预计会返回一个原语,因此哈希表在这里可以正常工作:

    function uniqBy(a, key) {
        var seen = {};
        return a.filter(function(item) {
            var k = key(item);
            return seen.hasOwnProperty(k) ? false : (seen[k] = true);
        })
    }
    

    一个特别有用的 key() 方法是 JSON.stringify 删除那些物理上不同但“看起来”相同的对象:

    a = [[1,2,3], [4,5,6], [1,2,3]]
    b = uniqBy(a, JSON.stringify)
    console.log(b) // [[1,2,3], [4,5,6]]
    

    如果 key 不是原始的,那么你必须采取线性搜索:

    function uniqBy(a, key) {
        var index = [];
        return a.filter(function (item) {
            var k = key(item);
            return index.indexOf(k) >= 0 ? false : index.push(k);
        });
    }
    

    在 ES6 中你可以使用 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);
        });
    }
    

    Map

    function uniqBy(a, key) {
        return [
            ...new Map(
                a.map(x => [key(x), x])
            ).values()
        ]
    }
    

    它们都可以和非原始键一起使用。

    第一个还是最后一个?

    当按键删除对象时,您可能希望保留第一个“相等”的对象或最后一个对象。

    使用 Set 上面的变体保留第一个,使用 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))

    图书馆

    Underscore underscore Lo-Dash 提供了 uniq 方法。它们的算法基本类似于上面的第一个代码片段,可以归结为以下内容:

    var result = [];
    a.forEach(function(item) {
         if(result.indexOf(item) < 0) {
             result.push(item);
         }
    });
    

    这是二次的,但有一些很好的附加优点,比如包装本机 indexOf 、按键唯一化的能力( iteratee 按照他们的说法),以及对已排序数组的优化。

    如果您正在使用 jQuery,并且无法忍受任何不花钱的东西,那么情况是这样的:

      $.uniqArray = function(a) {
            return $.grep(a, function(item, pos) {
                return $.inArray(item, a) === pos;
            });
      }
    

    这又是第一个片段的变体。

    表现

    JavaScript 中的函数调用非常昂贵,因此上述解决方案虽然简洁,但效率并不高。为了获得最佳性能,请用 filter 循环替换并删除其他函数调用:

    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;
    }
    

    这段丑陋的代码与上面的代码片段#3具有相同的功能,但速度要快一个数量级(截至 2017 年,它的速度只快两倍 - JS 核心人员做得很好!)

    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

    ES6 提供了 Set 对象,这使得事情变得简单多了:

    function uniq(a) {
       return Array.from(new Set(a));
    }
    

    或者

    let uniq = a => [...new Set(a)];
    

    请注意,与 Python 不同,ES6 集合按插入顺序进行迭代,因此此代码保留了原始数组的顺序。

    但是,如果您需要一个具有唯一元素的数组,为什么不从一开始就使用集合呢?

    生成器

    可以在相同基础上构建 uniq 一个基于生成器的“惰性”版本

    • 从参数中获取下一个值
    • 如果已经看过,请跳过
    • 否则,将其取出并添加到已见值集合中

    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
  • filter 和 indexOf 是在 ECMAScript 5 中引入的,因此这在旧版 IE(<9)中不起作用。如果你关心这些浏览器,你将不得不使用具有类似功能的库(jQuery、underscore.js 等)。

  • @RoderickObrist 如果你想让你的页面在旧版浏览器中运行,你可能会

  • 尝试这个数组:[\'toString\', \'valueOf\', \'failed\']。toString 和 valueOf 被完全剥离。使用 Object.create(null) 而不是 {}。

  • 厌倦了看到所有使用 for 循环或 jQuery 的糟糕示例。如今,JavaScript 拥有完美的工具:sort、map 和 Reduce。

    Uniq 减少同时保持现有顺序

    var names = ["Mike","Matt","Nancy","Adam","Jenny","Nancy","Carl"];
    
    var uniq = names.reduce(function(a,b){
        if (a.indexOf(b) < 0 ) a.push(b);
        return a;
      },[]);
    
    console.log(uniq, names) // [ 'Mike', 'Matt', 'Nancy', 'Adam', 'Jenny', 'Carl' ]
    
    // one liner
    return names.reduce(function(a,b){if(a.indexOf(b)<0)a.push(b);return a;},[]);
    

    更快的 uniq 排序

    可能还有其他更快的方法,但这个方法相当不错。

    var uniq = names.slice() // slice makes copy of array before sorting it
      .sort(function(a,b){
        return a > b;
      })
      .reduce(function(a,b){
        if (a.slice(-1)[0] !== b) a.push(b); // slice(-1)[0] means last item in array without removing it (like .pop())
        return a;
      },[]); // this empty array becomes the starting value for a
    
    // one liner
    return names.slice().sort(function(a,b){return a > b}).reduce(function(a,b){if (a.slice(-1)[0] !== b) a.push(b);return a;},[]);
    

    2015年更新:ES6版本:

    在 ES6 中,您可以使用 Sets 和 Spread,它们可以非常轻松且高效地删除所有重复项:

    var uniq = [ ...new Set(names) ]; // [ 'Mike', 'Matt', 'Nancy', 'Adam', 'Jenny', 'Carl' ]
    

    根据出现情况排序:

    有人询问如何根据唯一名称的数量对结果进行排序:

    var names = ['Mike', 'Matt', 'Nancy', 'Adam', 'Jenny', 'Nancy', 'Carl']
    
    var uniq = names
      .map((name) => {
        return {count: 1, name: name}
      })
      .reduce((a, b) => {
        a[b.name] = (a[b.name] || 0) + b.count
        return a
      }, {})
    
    var sorted = Object.keys(uniq).sort((a, b) => uniq[a] < uniq[b])
    
    console.log(sorted)
    
  • 太棒了!是否可以根据重复对象的频率对数组进行排序?这样,上例中的“Nancy”就被移到了修改后的数组的前面(或后面)了?

  • 在您的第二个示例中,sort() 似乎被错误调用:如果 a < b,则它返回的值与 a == b 相同,这可能导致未排序的结果。除非您在这里做了一些我未注意到的巧妙操作,否则应该是 .sort(function(a,b){ return a > b ? 1 : a < b ? -1 : 0; })

  • 如果数据只是名称数组,除了消除重复之外没有其他要求,那么为什么要费心进行排序、映射和归约呢?只需使用一个集合 - 工作在 O(n) 时间内完成。-- msdn.microsoft.com/en-us/library/dn251547

  • @Dave 是的 - 请参阅上面 [...new Set(names)] 的示例

  • Vanilla JS:使用集合之类的对象删除重复项

    您可以随时尝试将其放入一个对象中,然后遍历其键:

    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:通过跟踪已经看到的值删除重复项(顺序安全)

    或者,对于顺序安全的版本,使用对象来存储所有先前看到的值,并在添加到数组之前根据该值检查值。

    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:使用新的 Set 数据结构(顺序安全)

    ECMAScript 6 添加了新的 Set 数据结构,允许您存储任何类型的值。 Set.values 按插入顺序返回元素。

    function remove_duplicates_es6(arr) {
        let s = new Set(arr);
        let it = s.values();
        return Array.from(it);
    }
    

    使用示例:

    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"]
    
  • 在较新的浏览器中,您甚至可以执行 var c = Object.keys(b)。需要注意的是,这种方法仅适用于字符串,但没关系,这正是原始问题所要求的。

  • @JuanMendes 我创建了一个顺序安全的版本,如果该值以前没有出现过,它就会简单地复制到新数组中。

  • @kittu,这是获取数组的第 i 个元素,并将其放入对象中(用作集合)。键是元素,值为 true,这完全是任意的,因为我们只关心对象的键。

返回
作者最近主题: