惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
Spread Privacy
Spread Privacy
T
The Exploit Database - CXSecurity.com
Simon Willison's Weblog
Simon Willison's Weblog
P
Privacy & Cybersecurity Law Blog
L
LINUX DO - 热门话题
T
Threat Research - Cisco Blogs
T
Tenable Blog
TaoSecurity Blog
TaoSecurity Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
AI
AI
P
Proofpoint News Feed
A
About on SuperTechFans
P
Privacy International News Feed
月光博客
月光博客
雷峰网
雷峰网
S
Secure Thoughts
博客园 - 叶小钗
博客园 - 聂微东
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Project Zero
Project Zero
The Cloudflare Blog
SecWiki News
SecWiki News
The Hacker News
The Hacker News
V
Vulnerabilities – Threatpost
罗磊的独立博客
A
Arctic Wolf
阮一峰的网络日志
阮一峰的网络日志
Know Your Adversary
Know Your Adversary
酷 壳 – CoolShell
酷 壳 – CoolShell
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Troy Hunt's Blog
The Last Watchdog
The Last Watchdog
Schneier on Security
Schneier on Security
小众软件
小众软件
有赞技术团队
有赞技术团队
博客园 - 司徒正美
T
Tailwind CSS Blog
量子位
C
Cybersecurity and Infrastructure Security Agency CISA
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Hugging Face - Blog
Hugging Face - Blog
人人都是产品经理
人人都是产品经理
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
S
Security @ Cisco Blogs
大猫的无限游戏
大猫的无限游戏
S
SegmentFault 最新的问题
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
L
Lohrmann on Cybersecurity

Surmon.me

一人有限集团 你就是不敢 创造力是温柔的谎言 人类正在退出人类 AI 代替不了这样的你 脉冲点火背后的架构设计 基于 Cloudflare 生态的 AI Agent 实现 NodePress 支持用户登录了 从统计学习到通用智能 2025 投资报告:走慢的路 无依之地 会杀人的菩萨 无我不是共识 文化的积重与偏见 当下即安 科学的尽头是态度 无我不是 Egoless 信仰不因恐惧而存在 世间无解的矛与盾 先别急着做些什么 佛不需要你的皈依 真理的幻觉 两扇大门 造心里的浮屠 自胜者强 逻辑与智慧 真的相 快乐的秘密 只需愿意 是名体验 最深沉的梦 森林里倒下的树 现象与相 庸人自扰 React 与 Vue 的完美融合 开心就好吗 与原生家庭和解 两支毒箭 怎么自净其意 一尘不染 一片白云 让风穿膛而过 喝下去,然后闭嘴 活在当下 当纠结发生时 唯「我」独尊 一场把戏(时间) 一场把戏(二元对立) “我” 和 “我” 谁是众生? 船里没有人 玫瑰不需要说话 没有人能渡任何人 你我为轮,使之不再 当我们在谈论独立时 自我约束就是最好的自我接纳 从善与恶的表象出发 佛法不是心理学 别人眼中的你并不是你 高高山上走 祝你心想事成 祝你的噩梦早日发生 道是无情却有情 裙子只为自己而穿 不要盲目地评估自己 先别急着 “爱自己” 让生命欣然舞动 你无法复制乔布斯 符号不是目的 如果一件事发生了 每当我起心动念时 爱出者爱返 理解不是尊重的基础 在没有解决自己的问题之前 “知足常乐” 不是比较 逻辑是无法成为信仰的 买不到的自由 成为诵经者 伤口听不懂大道理 是立场让你不自由 - Unfreedom 天雨大不润无根之草 做自己的神 当心诱惑本身 牛洞冥思录 修罗启示录 让大脑自举 我更喜欢叫它无梦寺 存在主义也许不是解药 出南洋记 2022 的所有答复 没意义的表达 思考的记录 故宫暂行之幻想 极端的比较 解构的终极 舍利子是如何炼成的 心要野 佛教的偏见 文艺青年 我是动物
在 JavaScript 中学习数据结构与算法
Surmon · 2017-05-10 · via Surmon.me

这是一本 5 万字符(中文约 2w)的小书,可能需要几个小时阅读,需要几天或更多时间去消化。部分代码还不能正确地跑起来,有错别字,有不准确的概念...,但这不妨碍它作为你一个野生前端学习数据结构与算法的启蒙文章,期待你的一针见血、刀刀致命 😐


对任何专业技术人员来说,理解数据结构都非常重要。作为软件开发者,我们要能够用编程语言和数据结构来解决问题。编程语言和数据结构是这些问题解决方案中不可或缺的一部分。如果选择了不恰当的数据结构,可能会影响所写程序的性能。因此,了解不同数据结构和它们的适用范围十分重要。

一句话:算法即原力,即正义

本文主要讲述 JavaScript 中实现栈、队列、链表、集合、字典、散列表、树、图等数据结构,以及各种排序和搜索算法,包括冒泡排序、选择排序、插入排序、归并排序、快速排序、顺序搜索、二分搜索,最后还介绍了动态规划和贪心算法等常用的高级算法及相关知识。

在阅读之前假设你已了解并可以熟练使用 JavaScript 编写应用程序。

概览

数据结构

  • :一种遵从先进后出 (LIFO) 原则的有序集合;新添加的或待删除的元素都保存在栈的末尾,称作栈顶,另一端为栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底。
  • 队列:与上相反,一种遵循先进先出 (FIFO / First In First Out) 原则的一组有序的项;队列在尾部添加新元素,并从头部移除元素。最新添加的元素必须排在队列的末尾。
  • 链表:存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的;每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(指针/链接)组成。
  • 集合:由一组无序且唯一(即不能重复)的项组成;这个数据结构使用了与有限集合相同的数学概念,但应用在计算机科学的数据结构中。
  • 字典:以 [键,值] 对为数据形态的数据结构,其中键名用来查询特定元素,类似于 JavaScript 中的 Object
  • 散列:根据关键码值(Key value)直接进行访问的数据结构;它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度;这个映射函数叫做散列函数,存放记录的数组叫做散列表。
  • :由 n(n>=1)个有限节点组成一个具有层次关系的集合;把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的,基本呈一对多关系,树也可以看做是图的特殊形式。
  • :图是网络结构的抽象模型;图是一组由边连接的节点(顶点);任何二元关系都可以用图来表示,常见的比如:道路图、关系图,呈多对多关系。

算法

排序算法

  • 冒泡排序:比较任何两个相邻的项,如果第一个比第二个大,则交换它们;元素项向上移动至正确的顺序,好似气泡上升至表面一般,因此得名。
  • 选择排序:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,以此循环,直至排序完毕。
  • 插入排序:将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,此算法适用于少量数据的排序,时间复杂度为 O(n^2)。
  • 归并排序:将原始序列切分成较小的序列,只到每个小序列无法再切分,然后执行合并,即将小序列归并成大的序列,合并过程进行比较排序,只到最后只有一个排序完毕的大序列,时间复杂度为 O(n log n)。
  • 快速排序:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行上述递归排序,以此达到整个数据变成有序序列,时间复杂度为 O(n log n)。

搜索算法

  • 顺序搜索:让目标元素与列表中的每一个元素逐个比较,直到找出与给定元素相同的元素为止,缺点是效率低下。
  • 二分搜索:在一个有序列表,以中间值为基准拆分为两个子列表,拿目标元素与中间值作比较从而再在目标的子列表中递归此方法,直至找到目标元素。

其他

贪心算法:在对问题求解时,不考虑全局,总是做出局部最优解的方法。

动态规划:在对问题求解时,由以求出的局部最优解来推导全局最优解。

复杂度概念:一个方法在执行的整个生命周期,所需要占用的资源,主要包括:时间资源、空间资源。

数据结构

栈是一种遵从先进后出 (LIFO) 原则的有序集合;新添加的或待删除的元素都保存在栈的末尾,称作栈顶,另一端为栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底。

通俗来讲,一摞叠起来的书或盘子都可以看做一个栈,我们想要拿出最底下的书或盘子,一定要现将上面的移走才可以。

栈也被用在编程语言的编译器和内存中保存变变量、方法调用。

在 Javascript 中我们可以使用数组的原生方法实现一个栈/队列的功能,鉴于学习目的,我们使用类来实现一个栈。

        
        

12345678910111213141516171819202122232425262728293031323334353637383940

class Stack { constructor() { this.items = []; } // 入栈 push(element) { this.items.push(element); } // 出栈 pop() { return this.items.pop(); } // 末位 get peek() { return this.items[this.items.length - 1]; } // 是否为空栈 get isEmpty() { return !this.items.length; } // 尺寸 get size() { return this.items.length; } // 清空栈 clear() { this.items = []; } // 打印栈数据 print() { console.log(this.items.toString()); } }

使用栈类:

        
        

12345678910111213

// 实例化一个栈 const stack = new Stack(); console.log(stack.isEmpty); // true // 添加元素 stack.push(5); stack.push(8); // 读取属性再添加 console.log(stack.peek); // 8 stack.push(11); console.log(stack.size); // 3 console.log(stack.isEmpty); // false

队列

与栈相反,队列是一种遵循先进先出 (FIFO / First In First Out) 原则的一组有序的项;队列在尾部添加新元素,并从头部移除元素。最新添加的元素必须排在队列的末尾。

在现实中,最常见的例子就是排队,吃饭排队、银行业务排队、公车的前门上后门下机制...,前面的人优先完成自己的事务,完成之后,下一个人才能继续。

在计算机科学中,一个常见的例子就是打印队列。比如说我们需要打印五份文档。我们会打开每个文档,然后点击打印按钮。每个文档都会被发送至打印队列。第一个发送到打印队列的文档会首先被打印,以此类推,直到打印完所有文档。

同样的,我们在 Javascript 中实现一个队列类。

        
        

123456789101112131415161718192021222324252627282930313233

class Queue { constructor(items) { this.items = items || []; } enqueue(element) { this.items.push(element); } dequeue() { return this.items.shift(); } front() { return this.items[0]; } clear() { this.items = []; } get size() { return this.items.length; } get isEmpty() { return !this.items.length; } print() { console.log(this.items.toString()); } }

使用队列类:

        
        

1234567891011

const queue = new Queue(); console.log(queue.isEmpty); // true queue.enqueue("John"); queue.enqueue("Jack"); queue.enqueue("Camila"); console.log(queue.size); // 3 console.log(queue.isEmpty); // false queue.dequeue(); queue.dequeue(); queue.print(); // 'Camila'

优先队列

队列大量应用在计算机科学以及我们的生活中,我们在之前话题中实现的默认队列也有一些修改版本。

其中一个修改版就是优先队列。元素的添加和移除是基于优先级的。一个现实的例子就是机场登机的顺序。头等舱和商务舱乘客的优先级要高于经济舱乘客。在有些国家,老年人和孕妇(或 带小孩的妇女)登机时也享有高于其他乘客的优先级。

另一个现实中的例子是医院的(急诊科)候诊室。医生会优先处理病情比较严重的患者。通常,护士会鉴别分类,根据患者病情的严重程度放号。

实现一个优先队列,有两种选项:设置优先级,然后在正确的位置添加元素;或者用入列操作添加元素,然后按照优先级移除它们。在下面示例中,我们将会在正确的位置添加元素,因此可以对它们使用默认的出列操作:

        
        

123456789101112131415161718192021222324252627282930313233343536373839404142434445

class PriorityQueue { constructor() { this.items = []; } enqueue(element, priority) { const queueElement = { element, priority }; if (this.isEmpty) { this.items.push(queueElement); } else { const preIndex = this.items.findIndex( (item) => queueElement.priority < item.priority ); if (preIndex > -1) { this.items.splice(preIndex, 0, queueElement); } else { this.items.push(queueElement); } } } dequeue() { return this.items.shift(); } front() { return this.items[0]; } clear() { this.items = []; } get size() { return this.items.length; } get isEmpty() { return !this.items.length; } print() { console.log(this.items); } }

优先队列的使用:

        
        

12345678910

const priorityQueue = new PriorityQueue(); priorityQueue.enqueue("John", 2); priorityQueue.enqueue("Jack", 1); priorityQueue.enqueue("Camila", 1); priorityQueue.enqueue("Surmon", 3); priorityQueue.enqueue("skyRover", 2); priorityQueue.enqueue("司马萌", 1); priorityQueue.print(); console.log(priorityQueue.isEmpty, priorityQueue.size); // false 6

循环队列

为充分利用向量空间,克服"假溢出"现象的方法是:将向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量。存储在其中的队列称为循环队列(Circular Queue)。这种循环队列可以以单链表、队列的方式来在实际编程应用中来实现。

下面我们基于首次实现的队列类,简单实现一个循环引用的示例:

        
        

1234567891011121314

class LoopQueue extends Queue { constructor(items) { super(items); } getIndex(index) { const length = this.items.length; return index > length ? index % length : index; } find(index) { return !this.isEmpty ? this.items[this.getIndex(index)] : null; } }

访问一个循环队列:

        
        

12345678

const loopQueue = new LoopQueue(["Surmon"]); loopQueue.enqueue("SkyRover"); loopQueue.enqueue("Even"); loopQueue.enqueue("Alice"); console.log(loopQueue.size, loopQueue.isEmpty); // 4 false console.log(loopQueue.find(26)); // 'Evan' console.log(loopQueue.find(87651)); // 'Alice'

链表

要存储多个元素,数组(或列表)可能是最常用的数据结构。 每种语言都实现了数组。这种数据结构非常方便,提供了一个便利的 [] 语法来访问它的元素。 然而,这种数据结构有一个缺点:在大多数语言中,数组的大小是固定的,从数组的起点或中间插入或移除项的成本很高,因为需要移动元素; 尽管 JavaScript 中的 Array 类方法可以帮我们做这些事,但背后的处理机制同样如此。

链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个 元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。下图展示了链表的结构:

相对于传统的数组,链表的一个好处在于,添加或移除元素的时候不需要移动其他元素。然而,链表需要使用指针,因此实现链表时需要额外注意。

数组的另一个细节是可以直接访问任何位置的任何元素,而要想访问链表中间的一个元素,需要从起点(表头)开始迭代列表直到找到所需的元素。

现实中有许多链表的例子:一列火车是由一系列车厢/车皮组成的,每节车厢/车皮都相互连接,你很容易分离一节车皮,改变它的位置,添加或移除它。下图演示了一列火车,每节车皮都是列表的元素,车皮间的连接就是指针:

下面我们使用 Javascript 创建一个链表类:

        
        

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115

// 链表节点 class Node { constructor(element) { this.element = element; this.next = null; } } // 链表 class LinkedList { constructor() { this.head = null; this.length = 0; } // 追加元素 append(element) { const node = new Node(element); let current = null; if (this.head === null) { this.head = node; } else { current = this.head; while (current.next) { current = current.next; } current.next = node; } this.length++; } // 任意位置插入元素 insert(position, element) { if (position >= 0 && position <= this.length) { const node = new Node(element); let current = this.head; let previous = null; let index = 0; if (position === 0) { this.head = node; } else { while (index++ < position) { previous = current; current = current.next; } node.next = current; previous.next = node; } this.length++; return true; } return false; } // 移除指定位置元素 removeAt(position) { // 检查越界值 if (position > -1 && position < length) { let current = this.head; let previous = null; let index = 0; if (position === 0) { this.head = current.next; } else { while (index++ < position) { previous = current; current = current.next; } previous.next = current.next; } this.length--; return current.element; } return null; } // 寻找元素下标 findIndex(element) { let current = this.head; let index = -1; while (current) { if (element === current.element) { return index + 1; } index++; current = current.next; } return -1; } // 删除指定文档 remove(element) { const index = this.indexOf(element); return this.removeAt(index); } isEmpty() { return !this.length; } size() { return this.length; } // 转为字符串 toString() { let current = this.head; let string = ""; while (current) { string += ` ${current.element}`; current = current.next; } return string; } }

链表类的使用:

        
        

1234567891011

const linkedList = new LinkedList(); console.log(linkedList); linkedList.append(2); linkedList.append(6); linkedList.append(24); linkedList.append(152); linkedList.insert(3, 18); console.log(linkedList); console.log(linkedList.findIndex(24));

双向链表

链表有多种不同的类型,这一节介绍双向链表。双向链表和普通链表的区别在于,在链表中, 一个节点只有链向下一个节点的链接,而在双向链表中,链接是双向的:一个链向下一个元素, 另一个链向前一个元素,如下图所示:

双向链表提供了两种迭代列表的方法:从头到尾,或者反过来。我们也可以访问一个特定节 点的下一个或前一个元素。在单向链表中,如果迭代列表时错过了要找的元素,就需要回到列表 起点,重新开始迭代。这是双向链表的一个优点。

我们继续来实现一个双向链表类:

        
        

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293

// 链表节点 class Node { constructor(element) { this.element = element; this.prev = null; this.next = null; } } // 双向链表 class DoublyLinkedList { constructor() { this.head = null; this.tail = null; this.length = 0; } // 任意位置插入元素 insert(position, element) { if (position >= 0 && position <= this.length) { const node = new Node(element); let current = this.head; let previous = null; let index = 0; // 首位 if (position === 0) { if (!head) { this.head = node; this.tail = node; } else { node.next = current; this.head = node; current.prev = node; } // 末位 } else if (position === this.length) { current = this.tail; current.next = node; node.prev = current; this.tail = node; // 中位 } else { while (index++ < position) { previous = current; current = current.next; } node.next = current; previous.next = node; current.prev = node; node.prev = previous; } this.length++; return true; } return false; } // 移除指定位置元素 removeAt(position) { if (position > -1 && position < this.length) { let current = this.head; let previous = null; let index = 0; // 首位 if (position === 0) { this.head = this.head.next; this.head.prev = null; if (this.length === 1) { this.tail = null; } // 末位 } else if (position === this.length - 1) { this.tail = this.tail.prev; this.tail.next = null; // 中位 } else { while (index++ < position) { previous = current; current = current.next; } previous.next = current.next; current.next.prev = previous; } this.length--; return current.element; } else { return null; } } }

循环链表

循环链表可以像链表一样只有单向引用,也可以像双向链表一样有双向引用。循环链表和链表之间唯一的区别在于,最后一个元素指向下一个元素的指针(tail.next)不是引用 null, 而是指向第一个元素(head),如下图所示。

双向循环链表有指向 head 元素的 tail.next,和指向 tail 元素的 head.prev。

链表相比数组最重要的优点,那就是无需移动链表中的元素,就能轻松地添加和移除元素。因此,当你需要添加和移除很多元素 时,最好的选择就是链表,而非数组。

集合

集合是由一组无序且唯一(不能重复)的项组成的。这个数据结构使用了与有限集合相同的数学概念,但应用在计算机科学的数据结构中。

在数学中,集合是一组不同的对象(的集)。

比如说:一个由大于或等于 0 的证书组成的自然数集合:N = { 0, 1, 2, 3, 4, 5, 6, ... },集合中的对象列表用 {} 包围。

集合是由一组一(即不能重的项组成的。这个数据结构使用了与有..合相同的数学..,但应用在.算..学的数据结构中。

目前 ES6 中已内置了 Set 类型的实现,出于学习目的,下面我们依旧使用 Javascript 创建一个集合类:

        
        

123456789101112131415161718192021222324252627282930313233

class Set { constructor() { this.items = {}; } has(value) { return this.items.hasOwnProperty(value); } add(value) { if (!this.has(value)) { this.items[value] = value; return true; } return false; } remove(value) { if (this.has(value)) { delete this.items[value]; return true; } return false; } get size() { return Object.keys(this.items).length; } get values() { return Object.keys(this.items); } }

使用集合类:

        
        

12345678910111213

const set = new Set(); set.add(1); console.log(set.values); // ["1"] console.log(set.has(1)); // true console.log(set.size); // 1 set.add(2); console.log(set.values); // ["1", "2"] console.log(set.has(2)); // true console.log(set.size); // 2 set.remove(1); console.log(set.values); // ["2"] set.remove(2); console.log(set.values); // []

对集合可以进行如下操作:

  • 并集:对于给定的两个集合,返回一个包含两个集合中所有元素的新集合。‰
  • 交集:对于给定的两个集合,返回一个包含两个集合中 Р 有元素的新集合。‰
  • 差集:对于给定的两个集合,返回一个包含所有存在于第一个集合且不存在于第二个集合的元素的新集合。‰
  • 子集:求证一个给定集合是否是另一集合的子集。

并集

并集的数学概念:集合 A 和 B 的并集,表示为 A∪B,定义如下:A∪B = { x | x∈A ∨ x∈B },意思是 x(元素)存在于 A 中,或 x 存在于 B 中。如图:

我们基于刚才的 Set 类实现一个并集方法:

        
        

123456

union(otherSet) { const unionSet = new Set() this.values.forEach((v, i) => unionSet.add(this.values[i])) otherSet.values.forEach((v, i) => unionSet.add(otherSet.values[i])) return unionSet }

交集

并集的数学概念:集合 A 和 B 的交集,表示为 A∩B,定义如下:A∩B = { x | x∈A ∧ x∈B },意思是 x(元素)存在于 A 中,且 x 存在于 B 中。如图:

我们基于刚才的 Set 类实现一个交集方法:

        
        

123456789

intersection(otherSet) { const intersectionSet = new Set() this.values.forEach((v, i) => { if (otherSet.has(v)) { intersectionSet.add(v) } }) return intersectionSet }

差集

差集的数学概念:集合 A 和 B 的差集,表示为 A-B,定义如下:A-B = { x | x∈A ∧ x∉B },意思是 x(元素)存在于 A 中,且不 x 存在于 B 中。如图:

我们基于刚才的 Set 类实现一个差集方法:

        
        

123456789

difference(otherSet) { const differenceSet = new Set() this.values.forEach((v, i) => { if (!otherSet.has(v)) { differenceSet.add(v) } }) return differenceSet }

子集

子集的数学概念:集合 A 是 B 的子集,或者说集合 B 包含了集合 A,如图:

我们基于刚才的 Set 类实现一个子集方法:

        
        

1234567

subset(otherSet) { if (this.size > otherSet.size) { return false } else { return !this.values.some(v => !otherSet.has(v)) } }

字典

集合、字典、散列表都可以存储不重复的数据。字典和我们上面实现的集合很像,上面的集合中我们以 { value: value } 的形式存储数据,而字典是以 { key: value } 的形式存储数据,字典也称作映射。

简单说:Object 对象便是字典在 Javascript 中的实现。

还是简单实现一个字典类:

        
        

12345678910111213141516171819202122232425262728293031323334

class Dictionary { constructor() { this.items = {}; } set(key, value) { this.items[key] = value; } get(key) { return this.items[key]; } remove(key) { delete this.items[key]; } get keys() { return Object.keys(this.items); } get values() { /* 也可以使用ES7中的values方法 return Object.values(this.items) */ // 在这里我们通过循环生成一个数组并输出 return Object.keys(this.items).reduce((r, c, i) => { r.push(this.items[c]); return r; }, []); } }

使用字典类:

        
        

123456789

const dictionary = new Dictionary(); dictionary.set("Gandalf", "gandalf@email.com"); dictionary.set("John", "johnsnow@email.com"); dictionary.set("Tyrion", "tyrion@email.com"); console.log(dictionary); console.log(dictionary.keys); console.log(dictionary.values); console.log(dictionary.items);