图是由具有边的节点集合组成的数据结构。图可以是有向的或者是无向的。
有向图包含功能类似于单行道的边。边缘从一个节点流向另一个节点。
比如,你可能有一个(关于)人物和电影的图表,其中每个人可以有多个喜欢的电影,但是电影没有喜欢的人。
无向图包含双向流动的边缘,类似于双向道路,两个方向都有交通。
比如,你可能有一个宠物图表,其中每只宠物都有一个所有者,每个所有者都有一只宠物。
备注:(下面)双向箭头代表一条边,但是为了显而易见,我绘制了两条箭头。
图(graph)中没有明确的信息层次结构。
方法
我们将创建一个(关于)人和冰淇凌口味的图表。这将是一个有向图,因为人们可以喜欢某些口味,但是味道可不喜欢人。
我们将创建三个类:
PersonNode
IceCreamFlavorNode
Graph
PersonNode
PersonNode
类将接受一个参数:一个人的名字。这将作为其标识符。
PersonNode
构造函数将包含两个属性:
-
name
:唯一标识符 -
favoriteFlavors
:关于IceCreamFlavorNodes的数组
另外,PersonNode
类包含一个方法:addFlavor
。这将传入一个参数,一个IceCreamFlavorNode
对象,并将其添加到数组favoriteFlavors
中。
类的定义如下所示:
class PersonNode {
constructor(name) {
this.name = name;
this.favoriteFlavors = [];
}
addFlavor(flavor) {
this.favoriteFlavors.push(flavor);
}
}
IceCreamFlavorNode
IceCreamFlavorNode
类将传入一个参数:冰淇凌口味。这将作为其标识符。
这个类不需要包含任何方法,因为这是一个无向图,数据是从person
流向flavors
,但是不会回流。
这个类的定义如下:
class IceCreamFlavorNode {
constructor(flavor) {
this.flavor = flavor;
}
}
Graph
Graph
类不接受任何参数,但是其构造函数将包含三个属性:
-
peopleNodes
:人物节点数组。 -
iceCreamFlavorNodes
:冰淇凌口味节点数组。 -
edges
:包含PersonNodes
和IceCreamFlavorNodes
之间的边缘数组。
Graph
类将包含六个方法:
-
addPersonNode(name)
:接受一个参数,一个人的名字,创建一个具有此名字的PersonNode
对象,并将其推送到peopleNodes
数组。 -
addIceCreamFlavorNode(flavor)
:接受一个参数,一个冰淇凌口味,创建一个具有这种口味的IceCreamFlavorNode
对象,并将其推送到iceCreamFlavorNodes
数组中。 -
getPerson(name)
:接受一个参数,一个人名字,并返回该人的节点。 -
getFlavor(flavor)
:接受一个参数,一个冰淇凌口味,并返回该口味的节点。 -
addEdge(personName, flavorName)
:接受两个参数,一个人的名称和一个冰淇凌口味,检索两个节点,将flavor
添加到人的favoriteFlavors
数组,并将边推送到edge
数组。 -
print()
:简单打印出peopleNodes
数组中的每个人,以及他们最喜欢的冰淇凌口味。
类的定义如下所示:
class Graph {
constructor() {
this.peopleNodes = [];
this.iceCreamFlavorNodes = [];
this.edges = [];
}
addPersonNode(name) {
this.peopleNodes.push(new PersonNode(name));
}
addIceCreamFlavorNode(flavor) {
this.iceCreamFlavorNodes.push(new IceCreamFlavorNode(flavor));
}
getPerson(name) {
return this.peopleNodes.find(person => person.name === name);
}
getFlavor(flavor) {
return this.iceCreamFlavorNodes.find(flavor => flavor === flavor);
}
addEdge(personName, flavorName) {
const person = this.getPerson(personName);
const flavor = this.getFlavor(flavorName);
person.addFlavor(flavor);
this.edges.push(`${personName} - ${flavorName}`);
}
print() {
return this.peopleNodes.map(({ name, favoriteFlavors }) => {
return `${name} => ${favoriteFlavors.map(flavor => `${flavor.flavor},`).join(' ')}`;
}).join('\n')
}
}
虚拟数据
现在,我们有了三个类,我们可以添加一些数据并测试它们:
const graph = new Graph(true);
graph.addPersonNode('Emma');
graph.addPersonNode('Kai');
graph.addPersonNode('Sarah');
graph.addPersonNode('Maranda');
graph.addIceCreamFlavorNode('Chocolate Chip');
graph.addIceCreamFlavorNode('Strawberry');
graph.addIceCreamFlavorNode('Cookie Dough');
graph.addIceCreamFlavorNode('Vanilla');
graph.addIceCreamFlavorNode('Pistachio');
graph.addEdge('Emma', 'Chocolate Chip');
graph.addEdge('Emma', 'Cookie Dough');
graph.addEdge('Emma', 'Vanilla');
graph.addEdge('Kai', 'Vanilla');
graph.addEdge('Kai', 'Strawberry');
graph.addEdge('Kai', 'Cookie Dough');
graph.addEdge('Kai', 'Chocolate Chip');
graph.addEdge('Kai', 'Pistachio');
graph.addEdge('Maranda', 'Vanilla');
graph.addEdge('Maranda', 'Cookie Dough');
graph.addEdge('Sarah', 'Strawberry');
console.log(graph.print());
下面是我们有向图看起来类似(的样子):
如果你想看完整的代码,到我的CodePen上查看。
后话
原文:https://dev.to/emmawedekind/creating-graphs-with-javascript-4efm