如何高效删除 JavaScript 数组中的重复元素?

news2024/9/20 14:58:17

05eb3b7cde6550627df83ce198dda87c.jpeg

在日常编程中,我们经常会遇到数组去重的问题。今天,我们就来聊聊如何用JavaScript来优雅地解决这个问题。

问题描述

给定一个包含重复元素的数组,我们希望创建一个新的数组,其中只包含原始数组中的唯一值。例如,如果我们有一个数组 [1, 2, 3, 2, 4, 1, 5],期望的输出应该是 [1, 2, 3, 4, 5]

方法一:最原始的方法

我们可以使用最简单的方法——嵌套循环来解决这个问题。遍历每一个元素,检查它是否已经存在于新数组中,如果不存在则添加进去。

function removeDuplicates(arr) {
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    let isDuplicate = false;
    for (let j = 0; j < result.length; j++) {
      if (arr[i] === result[j]) {
        isDuplicate = true;
        break;
      }
    }
    if (!isDuplicate) {
      result.push(arr[i]);
    }
  }
  return result;
}
const myArray = [1, 2, 3, 2, 4, 1, 5];
const uniqueArray = removeDuplicates(myArray);
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

这个方法虽然直观,但当数组很大时,效率会变得非常低,因为时间复杂度是 O(n²)。

方法二:使用indexOf和filter方法

我们还可以使用 indexOf 方法配合 filter 方法来去重,这样看起来会简洁不少。

function removeDuplicates(arr) {
  return arr.filter((item, pos) => arr.indexOf(item) === pos);
}
const myArray = [1, 2, 3, 2, 4, 1, 5];
const uniqueArray = removeDuplicates(myArray);
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

在这个方法中,我们使用 filter 方法创建了一个新数组,只有满足条件的元素才会被包含进来。条件是当前元素的索引应该等于该元素在数组中第一次出现的位置。这种方法代码看起来更简洁,但是它的时间复杂度依然是 O(n²),因为 indexOf 需要遍历整个数组来查找元素的位置。

使用对象特性优化

在处理大数组去重时,我们可以利用对象的特性来提升性能。通过在对象中记录数组元素,可以有效减少重复元素的检查次数。

function removeDuplicates(arr) {
  const seen = {};
  return arr.filter((item) => {
    if (seen[item]) {
      return false;
    } else {
      seen[item] = true;
      return true;
    }
  });
}
const myArray = [1, 2, 3, 2, 4, 1, 5];
const uniqueArray = removeDuplicates(myArray);
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

这个方法创建了一个空对象 seen,然后通过 filter 方法遍历数组。每个元素都会检查是否已存在于 seen 对象中。如果存在,则跳过;否则,加入 seen 对象并保留在新数组中。这种方法对于大数组更高效,但存在一些缺点:

  • 类型转换:对象键只能是字符串或符号,这导致数字和字符串形式的数字无法区分。例如,removeDuplicates([1, "1"]) 会返回 [1]

  • 对象相等性:所有对象在这个解决方案中被认为是相等的。例如,removeDuplicates([{foo: 1}, {foo: 2}]) 会返回 [{foo: 1}]

如果你的数组只包含基本类型,并且不需要区分类型,这可以放心使用这个方法。

结合对象和数组的线性搜索

我们可以结合对象和数组的线性搜索方法来解决上述问题。

function removeDuplicates(arr) {
  const prims = { boolean: {}, number: {}, string: {} };
  const objs = [];
  return arr.filter((item) => {
    const type = typeof item;
    if (type in prims) {
      if (prims[type].hasOwnProperty(item)) {
        return false;
      } else {
        prims[type][item] = true;
        return true;
      }
    } else {
      if (objs.indexOf(item) >= 0) {
        return false;
      } else {
        objs.push(item);
        return true;
      }
    }
  });
}
const myArray = [1, 2, 3, 2, 4, 1, 5, { foo: 1 }, { foo: 2 }];
const uniqueArray = removeDuplicates(myArray);
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5, { foo: 1 }, { foo: 2 }]

主要优点

  • 分类存储:通过将基本类型和对象类型分别存储,减少了不同类型之间的冲突,逻辑清晰。

  • 高效处理基本类型:使用对象存储基本类型,查找和存储操作的时间复杂度为 O(1),效率较高。

存在的问题

  • 1、对象类型处理问题:

    • 引用比较:代码使用 indexOf 方法判断对象是否存在于数组中,这实际上是比较对象的引用而不是内容。即使两个对象内容相同,但引用不同,indexOf 也会返回 -1,导致内容相同但引用不同的对象被认为是不同的。例如,{ foo: 1 } 和另一个 { foo: 1 } 会被当作两个不同的对象。

    • 性能问题:对于大量对象类型的元素,由于 indexOf 方法需要遍历整个数组,时间复杂度为 O(n),性能较差。

  • 2、不能深度比较:对于嵌套对象或数组,该方法无法进行深度比较。例如,{ foo: [1, 2] } 和 { foo: [1, 2] } 这样的对象,内容相同但引用不同,会被认为是不同的对象。

最终方案:编写深度比较函数

编写深度比较函数 isDeepDataStructureEquality,用来比较两个对象的内容是否相同。

function isDeepDataStructureEquality(a, b) {
    let isEqual = Object.is(a, b);

    if (!isEqual) {
      if (Array.isArray(a) && Array.isArray(b)) {

        isEqual = (a.length === b.length) && a.every(
          (item, idx) => isDeepDataStructureEquality(item, b[idx])
        );
      } else if (
        a && b
        && (typeof a === 'object')
        && (typeof b === 'object')
      ) {
        const aKeys = Object.keys(a);
        const bKeys = Object.keys(b);

        isEqual = (aKeys.length === bKeys.length) && aKeys.every(
          (key, idx) => isDeepDataStructureEquality(a[key], b[key])
        );
      }
    }
    return isEqual;
  }

function removeDuplicates(arr) {
  const primitives = { boolean: {}, number: {}, string: {} };
  const objs = [];
  return arr.filter(item => {
    const type = typeof item;
    if (type in primitives) {
      if (primitives[type].hasOwnProperty(item)) {
        return false;
      } else {
        primitives[type][item] = true;
        return true;
      }
    } else {
      if (objs.some(obj => isDeepDataStructureEquality(obj, item))) {
        return false;
      } else {
        objs.push(item);
        return true;
      }
    }
  });
}

方法三:排序去重

另一种去重方法是先排序数组,然后去除连续重复的元素。

function removeDuplicates(arr) {
  return arr.sort().filter((item, pos, ary) => !pos || item !== ary[pos - 1]);
}
const myArray = [1, 2, 3, 2, 4, 1, 5];
const uniqueArray = removeDuplicates(myArray);
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

这个方法首先使用 sort 方法对数组进行排序,然后使用 filter 方法去除连续的重复元素。虽然对已排序的数组很有效,但无法处理对象数组。

方法四:使用 Set 处理对象

对于包含对象的数组,我们可以利用 Set 数据结构来高效去重。因为 Set 只存储唯一值,我们可以将数组转换为 Set,然后再转换回数组。

function removeDuplicates(arr) {
  return [...new Set(arr)];
}
const myArray = [1, 2, 3, 2, 4, 1, 5, { foo: 1 }, { foo: 2 }];
const uniqueArray = removeDuplicates(myArray);
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5, { foo: 1 }, { foo: 2 }]

这个方法通过 new Set(arr) 创建一个新的集合,然后使用扩展运算符 ... 将集合展开为数组,去重过程简单且高效。

  • 优点

    • 简洁:代码非常简洁,只需一行代码即可实现数组去重。

    • 高效:Set 数据结构在插入元素时自动去重,性能较好,时间复杂度为 O(n)。

  • 存在的问题

    • 对象引用问题:Set 判断元素是否相等时,使用的是同一对象引用。例如,两个内容相同但引用不同的对象 { foo: 1 } 和 { foo: 1 } 会被视为不同的元素。

总结

在实际开发中,选择合适的数组去重方法非常重要。如果数组主要包含基本类型,使用 Set 是一种简洁高效的选择。如果数组中包含复杂结构的对象,可以结合深度比较函数来确保去重的准确性。

无论你选择哪种方法,都要根据具体的应用场景和数据特点来决定。希望这些方法能帮助你在实际开发中更优雅地解决数组去重问题。如果你有其他更好的方法或建议,欢迎在评论区分享哦!💬

如果你喜欢这篇文章,请点赞并关注,更多前端技巧和小妙招等着你哦!😊

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1937129.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

十二、数组(2)

1.冒泡排序数组&#xff08;升序&#xff09; 冒泡排序&#xff1a;将一个整型数组排序&#xff08;升序&#xff09; 例&#xff1a; 10 9 8 7 6 5 4 3 2 1 9 10 8 7 6 …

Android11 framework 禁止三方应用开机自启动

Android11应用自启动限制 大纲 Android11应用自启动限制分析验证猜想&#xff1a;Android11 AOSP是否自带禁止三方应用监听BOOT_COMPLETED​方案禁止执行非系统应用监听到BOOT_COMPLETED​后的代码逻辑在执行启动时判断其启动的广播接收器一棍子打死方案&#xff08;慎用&#…

【Windows】操作系统之进程(第二篇)

目录 一、程序与进程的区别 一、定义与概念 二、主要区别 三、总结 二、进程的空间分配 1. 栈区&#xff08;Stack&#xff09; 2. 堆区&#xff08;Heap&#xff09; 3. 全局区&#xff08;静态区&#xff0c;Static Area&#xff09; 4. 文字常量区&#xff08;Text …

抓紧上车!中国学者用最新数据发一区top | GBD数据库周报(7.10~7.16)

全球疾病负担&#xff08;GBD&#xff09;是迄今为止规模最大、最全面的一项研究&#xff0c;旨在量化不同地区和不同时期的健康损失&#xff0c;从而改善卫生系统并消除差异。 该研究由华盛顿大学健康指标与评估研究所 (IHME) 牵头&#xff0c;是一项真正的全球性研究&#xf…

JVM:常用工具总结

文章目录 一、jstat工具 一、jstat工具 Jstat工具是JDK自带的一款监控工具&#xff0c;可以提供各种垃圾回收、类加载、编译信息等不同的数据。使用方法为&#xff1a;jstat -gc进程ID每次统计的时间间隔&#xff08;毫秒&#xff09;统计次数。 C代表Capacity容量&#xff0c…

高性能系统架构设计之:多级缓存

前言 为了提高系统的性能&#xff0c;一般会引入“缓存机制”&#xff0c;将部分热点数据存入缓存中&#xff0c;用空间换取时间&#xff0c;以达到快速响应的目的。 其实&#xff0c;缓存的应用远远不止存在于服务层&#xff08;传统的Redis缓存&#xff09;&#xff0c;从客户…

AIGC Kolors可图IP-Adapter-Plus风格参考模型使用案例

参考: https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-Plus 代码环境安装: git clone https://github.com/Kwai-Kolors/Kolors cd Kolors conda create --name kolors python=3.8 conda activate kolors pip install -r requirements.txt python3 setup.py install…

2-39 基于matlab的二维拉普拉斯方程求解

基于matlab的二维拉普拉斯方程求解&#xff0c;用有限差分法求解二维拉普拉斯方程 所用的数值方案是空间二阶中心差分法 (5 点差分&#xff09;。输出三维求解结果。程序已调通&#xff0c;可直接运行。 2-39 matlab 二维拉普拉斯方程求解 - 小红书 (xiaohongshu.com)

PDF-Extract-Kit (PDF内容抽取开源项目)

Github 地址&#xff1a;https://github.com/opendatalab/PDF-Extract-Kit 整体介绍 PDF文档中包含大量知识信息&#xff0c;例如文本、表格、图像、公式等。此外&#xff0c;PDF的文档布局也相当复杂&#xff0c;页眉、页脚、表格标题、图片标题等等&#xff0c;提取高质量的…

Nature子刊 | ATAC-seq、RNA-seq和蛋白组联合分析揭示脂质激活转录因子PPARα在肾脏代偿性肥大的作用机制

2023年6月&#xff0c;美国国立心肺血液研究所的研究团队在Nature Communications上发表题为“Signaling mechanisms in renal compensatory hypertrophy revealed by multi-omics”的文章&#xff0c;该研究通过在单侧肾切除的小鼠模型中使用多组学方法&#xff08;蛋白质组学…

十一、面向对象进阶

文章目录 学习目标一、类方法和静态方法二、单例模式三、Python的继承3.1 继承的基本使用3.2 Python继承的特点3.3 私有属性的继承特点3.4 新式类和经典类四、对象相关的运算符和内置函数五、多态的使用5.1 子类重写父类的方法5.2 多态的使用学习目标 说出类方法和实例方法的区…

Spring中的IOC详解

文章目录 IOCIOC容器的工作原理Bean的生命周期Bean的自动装配AutowiredResourceInject 使用Spring底层组件 IOC Spring的核心之一是IOC&#xff0c;IOC全称为Inversion of Control&#xff0c;中文译为控制反转&#xff0c;是面向对象编程中的一种设计原则&#xff0c;可以用来…

栈和队列(一) ------基本概念,循环队列

目录 栈 python实现 队列 python实现 循环队列 力扣622- --循环队列 力扣20 ----有效括号判断 分析 代码 栈 python实现 在Python中实现一个栈&#xff08;Stack&#xff09;可以通过使用列表&#xff08;list&#xff09;来完成&#xff0c;因为列表提供了动态数…

【网络安全的神秘世界】Error:Archives directory /var/cache/apt/archives/partial is missing.

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 | 每天学会一个渗透测试工具 ✨问题描述 在kali中想要安装beef-xss软件包时&#xff0c;发生如下报错&#xff1a; Error: Archives directory /var/cac…

d3d12.dll 文件缺失如何解决?五种修复丢失问题的方法

d3d12.dll 文件缺失如何解决&#xff1f;它为什么会不见呢&#xff1f;今天&#xff0c;我们将探讨 d3d12.dll 文件的重要性、原因以及丢失时的解决策略。本文将全面介绍 d3d12.dll 文件&#xff0c;并提供五种修复丢失问题的方法。 d3d12.dll文件是什么的详细介绍 d3d12.dll …

【RAGFlow】Ubuntu系统下实现源码启动RAGFlow

一、RAGFlow 是什么&#xff1f; RAGFlow 是一款基于深度文档理解构建的开源 RAG&#xff08;Retrieval-Augmented Generation&#xff09;引擎。RAGFlow 可以为各种规模的企业及个人提供一套精简的 RAG 工作流程&#xff0c;结合大语言模型&#xff08;LLM&#xff09;针对用…

【C语言初阶】C语言数组基础:从定义到遍历的全面指南

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C语言 “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;C语言函数 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀数组 &#x1f4d2;1. 什么是数组…

【医学影像】X86+FPGA:支持AI医学影像设备应用的工控主板,赋能CT、MRI、X线、超声等医学影像设备

支持AI医学影像设备应用的工控主板 在我国人口老龄化问题不断加剧&#xff0c;对影像诊断需求持续增长&#xff0c;和国家利好高端医学影像市场发展的系列法规和政策接连出台的大环境下&#xff0c;AI医学影像设备产业迎来发展黄金期。紧跟发展大势&#xff0c;基于12/13代 In…

天途无人机林业应用解决方案

林业应用现状分析 森林环境较为复杂&#xff0c;人员无法快速到达现场&#xff0c;工作人员通常会面临监控盲区&#xff0c;林区爬山涉水困难多&#xff1b;森林防火重要性不可忽视&#xff0c;2019年全国共发生森林火灾2345起&#xff0c;森林防火仍为重中之重&#xff1b;环…

SAPUI5基础知识16 - 深入理解MVC架构

1. 背景 经过一系列的练习&#xff0c;相信大家对于SAPUI5的应用程序已经有了直观的认识&#xff0c;我们在练习中介绍了视图、控制器、模型的概念和用法。在本篇博客中&#xff0c;让我们回顾总结下这些知识点&#xff0c;更深入地理解SAPUI5的MVC架构。 首先&#xff0c;让…