今天,我们要讲的是数据结构与算法中的集合。

集合简介

什么是集合?与栈、队列、链表这些顺序数据结构不同,集合是一种无序且唯一的数据结构。集合有什么用?在 Python 中,我经常使用集合来给数组去重:

>>> list(set([1,1,2]))
[1, 2] 

当然,ES6中也实现了集合——Set,那么 JavaScript 集合风格的数组去重应该是这样:

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

貌似没有 Python 简约,不过简约谁比得过 Python 呢?哈哈!JavaScript 中有了 Set 总比没有强。想更多地了解 Set,可以看 MDN 文档—— Set

除此之外,我们还可以使用集合来执行并集、交集、差集、子集等操作。

用 JavaScript 编写集合类

尽管 ES6 已经实现了集合类——Set,不过我们还是自己写一个吧!

私有变量

为了存储无序且唯一的元素,我们使用一个对象 items 来作为私有变量:

function Set(){
  var items = {};
}

然后,将每个元素作为该对象的键和值。比如,一个集合包含 1,2 两个元素,那么该集合的数据结构就应该是:

{
  '1': 1,
  '2': 2
}

这样就保证了集合元素的无序且唯一

实现 has 、add、remove 方法

实现 has 方法(即判断集合中是否存在指定元素)、add 方法(向集合中添加不存在的元素)、remove 方法(删除集合中存在的元素),可以跑通如下测试:

var set = new Set();

expect(set.add(1)).toBeTruthy(); // 断言一
expect(set.add(1)).toBeFalsy(); // 断言二
expect(set.add(2)).toBeTruthy(); // 断言三

expect(set.has(1)).toBeTruthy(); // 断言四
expect(set.has(3)).toBeFalsy(); // 断言五

expect(set.remove(1)).toBeTruthy(); // 断言六
expect(set.remove(1)).toBeFalsy(); // 断言七

上述测试代码中的七个断言都需要判断元素是否存在于集合中。那么如何判断元素是否存在于集合中呢?答案是使用 hasOwnProperty 方法。

hasOwnProperty 这个方法可以用来检测一个对象是否含有特定的自身属性;和 in 运算符不同,该方法会忽略掉那些从原型链上继承到的属性。更多的用法可以参考 MDN 文档——Object.prototype.hasOwnProperty()

通过 hasOwnProperty 方法我们可以轻易实现 has 方法。有了 has 方法,addremove 方法仅仅就是一个条件判断和给对象 items 赋值的简单问题了。实现代码如下:

this.has = function (value) {
  return items.hasOwnProperty(value); // {1}
};

this.add = function (value) {
  if (!this.has(value)) {
    items[value] = value;
    return true;
  }
  return false;
};

this.remove = function (value) {
  if (this.has(value)) {
    delete items[value];
    return true;
  }
  return false;
};

实现 size 和 values 方法

实现 size 方法(返回集合元素个数)和 values 方法(返回集合所有值),跑通如下测试:

var set = new Set();
set.add(2);

expect(set.size()).toBe(1); // 断言一
expect(set.values()).toEqual(['2']); // 断言二

断言一返回集合元素个数,断言二以数组形式返回所有值。为了实现这个需求,我们需要使用 Object.keys() 方法来获取对象的属性。

Object.keys() 方法会返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,数组中属性名的排列顺序和使用 for-in 循环遍历该对象时返回的顺序一致 (顺序一致不包括数字属性)(两者的主要区别是 for-in 还会遍历出一个对象从其原型链上继承到的可枚举属性)。更多的用法请参考 MDN 文档——Object.keys()

所以编写的代码如下:

this.size = function () {
  return Object.keys(items).length;
};

this.values = function () {
  return Object.keys(items);
};

实现 union 方法

实现 union 方法(和另一个集合取并集),跑通如下测试:

var set = new Set();
set.add(1);
set.add(2);

var otherSet = new Set();
otherSet.add(3);

var unionSet = set.union(otherSet);
expect(unionSet.values()).toEqual(['1', '2', '3']);

通过上述测试,我们可以知道,1,23取并集是 1,2,3。那么如何用代码实现呢?其实很简单,只需要新建一个集合,然后遍历两个集合的元素,并添加到新集合即可,新集合会自动过滤已经存在的元素,自然而然就得到了并集。实现代码如下:

this.union = function (otherSet) {
  var unionSet = new Set();

  var values = this.values();
  for (var i = 0; i < values.length; i++) {
    unionSet.add(values[i]);
  }

  values = otherSet.values();
  for (i = 0; i < values.length; i++) {
    unionSet.add(values[i]);
  }

  return unionSet;
};

实现 intersection 方法

实现 intersection 方法(和另一个集合取交集),跑通如下测试:

var set = new Set();
set.add(1);
set.add(2);

var otherSet = new Set();
otherSet.add(2);
otherSet.add(3);

var intersectionSet = set.intersection(otherSet);
expect(intersectionSet.values()).toEqual(['2']);

上述测试代码,仅仅是将 setotherSet 两个集合取交集,最终为 2。实现思路非常简单,只需要新建一个集合,然后遍历 otherSet 的元素,只要在 set 中存在就添加到新集合中,最后返回新集合。实现代码:

this.intersection = function (otherSet) {
  var intersectionSet = new Set();

  var values = otherSet.values();
  for (var i = 0; i < values.length; i++) {
    if (this.has(values[i])) {
      intersectionSet.add(values[i]);
    }
  }
  return intersectionSet;
};

实现 difference 方法

实现 difference 方法(和另一个集合取差集),跑通如下测试:

var set = new Set();
set.add(1);
set.add(2);

var otherSet = new Set();
otherSet.add(2);
otherSet.add(3);

var differenceSet = set.difference(otherSet);
expect(differenceSet.values()).toEqual(['1']);

上述测试代码仅仅是将 setotherSet 两个集合取差集得到 1。实现思路非常简单,只需要新建一个集合,然后遍历 set 中的元素,如果元素不存在于 otherSet 中就添加到新集合中。实现代码如下:

this.difference = function (otherSet) {
  var differenceSet = new Set();

  var values = this.values();
  for (var i = 0; i < values.length; i++) {
    if (!otherSet.has(values[i])) {
      differenceSet.add(values[i]);
    }
  }
  return differenceSet;
};

实现 subset 方法

实现 subset 方法(判断是否是另一个集合的子集),跑通如下测试:

var set = new Set();
set.add(1);
set.add(2);
set.add(3);

var otherSet = new Set();
otherSet.add(2);
otherSet.add(3);

expect(set.subset(otherSet)).toBeFalsy(); // 断言一
set.remove(3);
expect(set.subset(otherSet)).toBeFalsy();  // 断言二
set.add(1);
expect(set.subset(otherSet)).toBeTruthy();  // 断言三

断言一判断 1,2,3 是否是 2,3 的子集,因为元素个数都比人家多,显然不是。断言二判断 1,2 是否是 2,3 的子集,因为 1 不在 2,3 中,所以也不是。断言四判断 2 是否是 2,3 的子集,显然是。实现代码如下:

this.subset = function (otherSet) {
  if (this.size() > otherSet.size()) {
    return false;
  } else {
    var values = this.values();
    for (var i = 0; i < values.length; i++) {
      if (!otherSet.has(values[i])) {
        return false;
      }
    }
    return true;
  }
};

至此,集合类就完成了!

教程示例代码及目录

示例代码:https://github.com/lewis617/javascript-datastructures-algorithms

目录:http://www.liuyiqi.cn/tags/数据结构与算法/

JavaScript 版数据结构与算法(四)集合的更多相关文章

  1. JavaScript 版数据结构与算法(二)队列

    今天,我们要讲的是数据结构与算法中的队列. 队列简介 队列是什么?队列是一种先进先出(FIFO)的数据结构.队列有什么用呢?队列通常用来描述算法或生活中的一些先进先出的场景,比如: 在图的广度优先遍历 ...

  2. JavaScript 版数据结构与算法(三)链表

    今天,我们要讲的是数据结构与算法中的链表. 链表简介 链表是什么?链表是一种动态的数据结构,这意味着我们可以任意增删元素,它会按需扩容.为何要使用链表?下面列举一些链表的用途: 因为数组的存储有缺陷: ...

  3. JavaScript 版数据结构与算法(一)栈

    今天,我们要讲的是数据结构与算法中的栈. 栈的简介 栈是什么?栈是一个后进先出(LIFO)的数据结构.栈有啥作用?栈可以模拟算法或生活中的一些后进先出的场景,比如: 十进制转二进制,你需要将余数倒序输 ...

  4. Android版数据结构与算法(四):基于哈希表实现HashMap核心源码彻底分析

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 存储键值对我们首先想到HashMap,它的底层基于哈希表,采用数组存储数据,使用链表来解决哈希碰撞,它是线程不安全的,并且存储的key只能有一个为 ...

  5. javascript实现数据结构与算法系列:栈 -- 顺序存储表示和链式表示及示例

    栈(Stack)是限定仅在表尾进行插入或删除操作的线性表.表尾为栈顶(top),表头为栈底(bottom),不含元素的空表为空栈. 栈又称为后进先出(last in first out)的线性表. 堆 ...

  6. JavaScript版EAN码校验算法

      <script type="text/javascript"> $(document).ready(function () { $("#btnCalc&q ...

  7. Java数据结构和算法(四)--链表

    日常开发中,数组和集合使用的很多,而数组的无序插入和删除效率都是偏低的,这点在学习ArrayList源码的时候就知道了,因为需要把要 插入索引后面的所以元素全部后移一位. 而本文会详细讲解链表,可以解 ...

  8. 数据结构和算法 – 10.集合

    集合: 联合.交叉.差异.子集 using System; using System.Collections; using System.Collections.Generic; using Syst ...

  9. 第一章:javascript: 数据结构与算法

    在前端工程师中,常常有一种声音,我们为什么要学数据结构与算法,没有数据结构与算法,我们一样很好的完成工作.实际上,算法是一个宽泛的概念,我们写的任何程序都可以称为算法,甚至往冰箱里放大象,也要通过开门 ...

随机推荐

  1. 你所不知道的 CSS 滤镜技巧与细节

    承接上一篇你所不知道的 CSS 动画技巧与细节,本文主要介绍 CSS 滤镜的不常用用法,希望能给读者带来一些干货! OK,下面直接进入正文.本文所描述的滤镜,指的是 CSS3 出来后的滤镜,不是 IE ...

  2. 【2017集美大学1412软工实践_助教博客】团队作业6——展示博客(Alpha版本)

    题目 团队作业6: http://www.cnblogs.com/happyzm/p/6791211.html 团队成绩 团队成员简介 项目地址 项目目标,包括典型用户.功能描述.预期用户数量 如何满 ...

  3. 201521123073 《Java程序设计》第6周学习总结

    1. 本章学习总结 2. 书面作业 1.clone方法 1.1 Object对象中的clone方法是被protected修饰,在自定义的类中覆盖clone方法时需要注意什么? 1.2 自己设计类时,一 ...

  4. 201521123061 《Java程序设计》第五周学习总结

    201521123061 <Java程序设计>第五周学习总结 1. 本周学习总结 1.1 尝试使用思维导图总结有关多态与接口的知识点. 1.2 可选:使用常规方法总结其他上课内容. 1.代 ...

  5. 201521123035《Java程序设计》第四周学习总结

    本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结其他上课内容. 本周还讲了注释与类设计.老师用例子向我们展示实际生活中一个类里面包含了哪些属性,并由此联想到如果自 ...

  6. 201521123110 《JAVA程序设计》第3周学习总结

    1.本章学习总结 ` ` 2.书面作业 1.代码阅读 public class Test1 { private int i = 1;//这行不能修改 private static int j = 2; ...

  7. 201521123054 《Java程序设计》 第十周学习总结

    1. 本周学习总结 2. 书面作业 题目4-2 1.1 截图你的提交结果(出现学号) 1.2 4-2中finally中捕获异常需要注意什么? 无论是否抛出异常,也无论从什么地方返回,finally语句 ...

  8. 201521123018 《Java程序设计》第10周学习总结

    1. 本章学习总结 你对于本章知识的学习总结 2. 书面作业 一.inally 题目4-2 1.1 截图你的提交结果(出现学号) 1.2 4-2中finally中捕获异常需要注意什么? 答: 4-2中 ...

  9. 银河麒麟操作系统打开VMware报vmmon无法编译

    使用银河麒麟操作系统打开VMware可能会报vmmon无法编译 这个时候... 将/usr/src/linux-headers-xxx/include/miscdevice.h第71行void改为in ...

  10. 1-SDK开发初探-8266

    先分享一个比较感动的事情 其实做实物是因为好多人看了我的文章之后还是会遇到各种各样的问题,然后呢真是让亲们搞的自己好累.......所以就想着如果亲们用自己做的板子,出现什么问题能够快速的解决,,而且 ...