Content Table

图-创建图

为了方便创建图,可以把图的边按照格式 startVertex1-endVertex1:weight1,startVertex2-endVertex2:weight2 保存为一个字符串,例如 A-B:16,B-C:10,C-D:3,D-E:4,E-F:8,F-A:14,B-G:7,C-G:6,E-G:2,F-G:9,A-G:12,C-E:5,解析字符串得到图的所有边,使用邻接表存储图的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package graph;

import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
* 图
*/
@Getter
@Setter
@Accessors(chain = true)
public class Graph {
Map<String, List<Edge>> adjacentList = new HashMap<>(); // 邻接表存储图: key 为顶点,Edge 为此顶点和临接点构成的边

/**
* 获取图的顶点
*
* @return 返回顶点的集合
*/
public Set<String> getVertices() {
return adjacentList.keySet();
}

/**
* 获取图的边,无向图中 2 条相同的边只输出一条
*
* @return 返回边的集合
*/
public Set<Edge> getEdges() {
return adjacentList.values().stream().flatMap(List::stream).collect(Collectors.toSet());
}

/**
* 获取传入的顶点的所有边
*
* @param vertex 顶点
* @return 返回边的数组
*/
public List<Edge> getVertexEdges(String vertex) {
return adjacentList.get(vertex);
}

/**
* 使用图的边构建图,边的格式为 start-end:weight,边之间使用逗号分隔,例如 A-B:10,A-G:5
*
* @param edges 图的所有边
* @return 返回图的对象
*/
public static Graph build(String edges) {
Graph graph = new Graph();

for (String edgeContent : StringUtils.split(edges, ",")) {
// 边: A-B:10
int indexOfDash = edgeContent.indexOf("-");
int indexOfColon = edgeContent.indexOf(":");
String vertex1 = edgeContent.substring(0, indexOfDash);
String vertex2 = edgeContent.substring(indexOfDash+1, indexOfColon);
double weight = Double.parseDouble(edgeContent.substring(indexOfColon+1));

// 找到顶点 vertex1 的边集,添加它的边
graph.adjacentList.putIfAbsent(vertex1, new LinkedList<>());
graph.adjacentList.get(vertex1).add(new Edge(vertex1, vertex2, weight));

// 找到顶点 vertex2 的边集,添加它的边
graph.adjacentList.putIfAbsent(vertex2, new LinkedList<>());
graph.adjacentList.get(vertex2).add(new Edge(vertex2, vertex1, weight));
}

return graph;
}

/**
* 图的边,由起点、终点和权重构成
*/
@Getter
@Setter
@Accessors(chain = true)
public static class Edge {
String start; // 边的起点
String end; // 边的终点
double weight; // 边的权重

public Edge(String start, String end, double weight) {
this.start = start;
this.end = end;
this.weight = weight;
}

/**
* 2 个顶点相同的边则为同一条边
*/
@Override
public boolean equals(Object obj) {
if (obj.getClass() != getClass()) {
return false;
}

Edge other = (Edge) obj;

if (this.start.equals(other.start) && this.end.equals(other.end)) {
return true;
}

if (this.start.equals(other.end) && this.end.equals(other.start)) {
return true;
}

return false;
}

@Override
public int hashCode() {
// start, end 从小到大排序
if (start.compareTo(end) < 0) {
return Objects.hash(start, end);
} else {
return Objects.hash(end, start);
}
}
}

public static void main(String[] args) {
Graph graph = Graph.build("A-B:16,B-C:10,C-D:3,D-E:4,E-F:8,F-A:14,B-G:7,C-G:6,E-G:2,F-G:9,A-G:12,C-E:5");
System.out.println(JSON.toJSONString(graph.getAdjacentList()));
System.out.println(graph.getVertices());
System.out.println(JSON.toJSONString(graph.getEdges()));
System.out.println(JSON.toJSONString(graph.getVertexEdges("A"), true));
}
}

下面是顶点 A 的所有边:

1
2
3
4
5
6
7
8
9
10
11
12
13
[{
"end":"B",
"start":"A",
"weight":16.0
},{
"end":"F",
"start":"A",
"weight":14.0
},{
"end":"G",
"start":"A",
"weight":12.0
}]

安装 MySQL

Mac

使用 Brew 安装、使用 Docker 安装:

  • 创建 mysql 的配置文件 /Users/Biao/Documents/workspace/Docker/mysql/config-file.cnf (参考下面的配置,去掉 [WinMySQLAdmin] 部分、basedirdatadir)
  • docker pull mysql:5.7.29
  • docker run --name mysql -v /Users/Biao/Documents/workspace/Docker/mysql:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=root -p 3306:3306 -d mysql:5.7.29
  • 进入 MySQL 容器: docker exec -it mysql bash,然后可以在里面执行 mysql -u root -p 访问 MySQL

Linux

使用 Yum 安装、使用 Docker 安装

Windows

使用 Docker 安装,下面介绍安装解压版:

  1. 下载解压 http://dev.mysql.com/downloads/mysql/

  2. 在 mysql 的根目录创建 data 目录和 my.ini 配置文件,参考最后面的配置文件内容

  3. 参考安装 MySQL: 以管理员身份运行 cmd(一定要用管理员身份运行,不然权限不够),通过命令,进入 mysql bin 目录

  4. 输入 mysqld --initialize-insecure --user=mysql 回车

  5. 输入 mysqld install 回车

  6. 启动 MySQL: 输入 net start mysql 回车,启动 mysql 服务,start 启动,stop 停止。启动出错时可参考 net start mysql发生系统错误 2,找不到指定文件

  7. 输入 mysql -u root -p ,回车,出现 Enter passwore: ,输入密码,由于刚安装,没有设置密码,直接回车 Enter 进入

  8. MySQL 5.7 root 用户密码修改

    1
    2
    3
    use mysql;
    update user set authentication_string=password('新密码') where user='root' and Host='localhost';
    flush privileges;

配置文件 my.ini 的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[WinMySQLAdmin]
Server=D:/mysql-5.7/bin/mysqld.exe

[mysqld]
# Only allow connections from localhost
bind-address = 0.0.0.0
max_connections = 2000

basedir=D:/mysql-5.7
datadir=D:/mysql-5.7/data

character-set-server=utf8mb4
init_connect='SET NAMES utf8mb4'

[mysql]
default-character-set=utf8mb4

[mysql.server]
default-character-set=utf8mb4

[mysql_safe]
default-character-set=utf8mb4

[client]
default-character-set=utf8mb4

注意: Windows 下必须配置 [WinMySQLAdmin]

打印二叉树

在学习二叉树、二叉排序树、AVL 树、红黑树等时,如果能够直观的看到树的结构,对于学习有非常大的帮助。利用 binary-tree-printer 化打印可视化的二叉树,更多方案可参考 How to print binary tree diagram?

依赖

1
implementation "com.github.afkbrb:binary-tree-printer:1.0.0"

打印

打印完全二叉树:

1
BTPrinter.printTree("1,2,3,4,5,#,#,6,7,8,1,#,#,#,#,#,#,2,3,4,5,6,7,8,9,10,11,12,13,14,15");

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        1              
/ \
2 3
/ \
/ \
4 5
/ \ / \
6 7 8 1
/ \
/ \
/ \
/ \
/ \
2 3
/ \ / \
/ \ / \
4 5 6 7
/ \ / \ / \ / \
8 9 10 11 12 13 14 15

字符串构建树

练习树的算法时,使用代码手动构造树比较麻烦,容易出错,用 JSON 来表示也不够方便,用直观的字符串来表示一颗树更简单 (用缩进来表示节点的父子关系),例如:

1
2
3
4
5
6
7
8
9
10
11
1
2
3
5
6
4
7
9
10
8
11

Vue 数组

对象的数组属性存在

一般情况下,data() 返回的 JSON 中 user 的数组属性 roles 预先定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<template>
<div class="about">
<Button @click="changeRole1">修改角色一</Button>
<Button @click="changeRole2">修改角色二</Button>

<div v-for="role in user.roles" :key="role.value">
{{ role.name }} - {{ role.value }}
</div>
</div>
</template>

<script>
export default {
data() {
return {
user: {
roles: [
{ name: 'student', value: 1 },
{ name: 'teacher', value: 2 },
]
}
};
},
methods: {
changeRole1() {
// [1] 直接给数组赋值,界面响应更新
this.user.roles = [{ name: 'president', value: 3 }];
},
changeRole2() {
// [2] 修改数组的内容,界面响应更新
this.user.roles.push({ name: 'admin', value: 4 });
}
}
};
</script>

点击按钮修改数组内容后界面立即响应。

iView 自定义主题

项目使用 Vue Cli 3 创建,SCSS 作为 Css Pre-processors,按照 iView 自定义主题文档 https://www.iviewui.com/docs/guide/theme 的步骤进行操作时报错了,下面是解决自定义主题的方法:

  1. 安装 Less: yarn add less less-loader --dev

  2. 在 vue.config.js 中配置 Less:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    module.exports = {
    css: {
    loaderOptions: { // 向 CSS 相关的 loader 传递选项
    less: {
    javascriptEnabled: true
    }
    }
    },
    };
  3. 创建文件 public/static/iview-theme.less,在里面覆盖需要修改的 iView 主题的变量 (默认字体为 12px,有点小,修改为 14px):

    1
    2
    3
    4
    @import '~iview/src/styles/index.less';

    @font-size-small: 14px;
    @btn-font-size : 14px;
  4. 在入口文件 main.js 内导入这个 less 文件:

    1
    2
    3
    4
    5
    6
    import 'iview/dist/styles/iview.css';
    import Vue from 'vue';
    import iView from 'iview';
    import '@/../public/static/css/iview-theme.less';

    Vue.use(iView);
  5. 启动项目,主题修改成功,但发现 Modal 中的字体还是 12px (modal.less 中写死了),Switch 的字体为 14px 有点大,使用下面的样式强制修改它们的字体大小:

    1
    2
    3
    4
    5
    6
    7
    .ivu-modal-body {
    font-size: 14px !important;
    }

    .ivu-switch-inner {
    font-size: 12px !important;
    }

完整的变量列表可以查看 默认样式变量,覆盖需要修改的变量即可 (提示: 删除 @import "color/colors" 这一行)。

Vue 中实现拖拽

Sortable is a JavaScript library for reorderable drag-and-drop lists,下面介绍和 Vue 的简单集成:

  1. 添加依赖: yarn add sortablejs

  2. 页面中引入 Sortable: import Sortable from 'sortablejs'

  3. HTML 中创建被拖拽的列表

  4. 使用被拖拽元素的容器创建 Sortable 对象: Sortable.create(element, config)

  5. onEnd 事件触发时,修改 Vue 管理的数据

    和 Vue 集成最关键的是拖拽结束后需要手动修 Vue 管理的数据,Sortable 不会帮我们修改,具体请参考 onEnd 函数。

Elasticsearch 入门

ElasticSearch (下面简称 ES) 是一个基于 Lucene 的全文检索服务器,本文简单的介绍 ES 的安装、配置、启动、一些基本概念、中文分词以及使用 Java 编程访问 ES 等。

安装配置启动

  1. 安装: 目前 spring-data-elasticsearch 最高支持 elasticsearch-6.2.2 (可参考最后的版本对应进行选择),所以下载 https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.2.2.zip,解压即可

  2. 配置: 修改配置文件 config/elasticsearch.yml (只介绍单机环境的,中小型应用足够了):

    1
    2
    3
    4
    cluster.name: ebag      # 集群名称
    node.name: node-1 # 节点名称
    network.host: 0.0.0.0 # 访问地址, 局域网需要访问
    http.port: 9200 # 端口
  3. 启动: elasticsearch -d,注意: Linux 下不允许使用 root 用户启动,可以创建一个用户如 elasticsearch,然后使用此用户启动 ES:

    • useradd elasticsearch
    • passwd elasticsearch
    • su elasticsearch
    • elasticsearch -d
  4. 浏览器中访问 http://localhost:9200,输出如下则说明 ES 启动成功:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    {
    "name": "node-1",
    "cluster_name": "ebag",
    "cluster_uuid": "Ogsv5NneTHyHmWDWM5hH5A",
    "version": {
    "number": "6.2.2",
    "build_hash": "10b1edd",
    "build_date": "2018-02-16T19:01:30.685723Z",
    "build_snapshot": false,
    "lucene_version": "7.2.1",
    "minimum_wire_compatibility_version": "5.6.0",
    "minimum_index_compatibility_version": "5.0.0"
    },
    "tagline": "You Know, for Search"
    }

启动时如果发生错误,可参考 https://www.jianshu.com/p/312dfaa3a27b

Async Validator

表单验证插件 jQuery Validation 一文中介绍过表单验证的库 jQuery Validation,这里简单的介绍另外一个表单验证的库 async-validator (iView 的表单验证也是使用了这个库),了解基础使用后,请阅读官方文档深入学习。

添加依赖

1
yarn add async-validator

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 1. 导入 async-validator
import Validator from 'async-validator';

// 要验证的数据对象
const org = {
name: '',
};

// 2. 定义验证规则
const rules = {
name: { type: 'string', required: true, whitespace: true, message: '机构名不能为空白字符' },
};

// 3. 使用验证规则创建验证器
const validator = new Validator(rules);

// 4. 调用 validate 方法验证数据
validator.validate(org).then(() => {
// 验证通过
console.log('success');
}).catch(({ errors, fields }) => {
// 验证失败
console.log(errors);
});

注意: require 为 true 时表示需要验证,为 false 表示不进行验证,required 默认值为 false。