| Yang |


  • Home

  • Tags

  • Archives

实现经典的排序算法

Posted on 2019-08-27

了解排序算法的原理,还是我辈的基本功,虽然暂时也不用面试,平常工作中也很少用到,但是积累下也总是好的。(默认从小到大排序)

冒泡排序


冒泡排序的基本原理很简单,它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。至于为啥叫这个名字呢,是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

算法步骤:

  • 比较相邻的元素,如果顺序不对则交换
  • 对每一对相邻的数做同样的工作,不出意外的话,最大的数会”浮“到最后的位置上
  • 针对所有元素重复以上步骤,除了最后一个
  • 重复步骤1-3,直到排序完成

Python实现

1
2
3
4
5
6
7
8

def bubble_sort(arr):
length = len(arr) # 获取数组长度
for i in range(length):
for j in range(1,length-i): # 对所有元素重复执行
if arr[j-1] > arr[j]: # 比较相邻的元素,如果前者比后者大
arr[j],arr[j-1] = arr[j-1],arr[j] # 则交换顺序
return arr

时间复杂度为 O(n^2 ) 的一个算法,不过最好的时间复杂度是 O(n) - 数组已经有序的情况。

选择排序


这是一种特别直观简单的排序方法,即在未排序序列中找到最小的元素放在起始位置,然后从剩下未排序序列中继续找到最小的元素,然后放到已排序序列的末尾。循环直到排序完毕

算法步骤:

  • 初始状态,无序区m[0,n-1],有序区n[]
  • 第i趟排序,无序区m[i,n-1],有序区n[0,i-1],则这一趟我们把其中找到的最小的元素与m[i](无序区的第一个位置)交换
  • 循环n-1趟

Python实现

1
2
3
4
5
6
7
8
9
10

def select_sort(arr):
n = len(arr)
for i in range(n):
min_index = i # 第i个最小的元素的下标
for j in range(i+1,n):
if arr[j] < arr[min_index]: # 寻找最小的数
min_index = j #保存索引
arr[i],arr[min_index]= arr[min_index],arr[i]
return arr

算法复杂度O(n^2),最好最坏都是,非常稳定。

插入排序

它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

算法步骤:

  • 默认第一个元素为已排序
  • 取出下一个元素,从后往前扫描
  • 如果该元素(已排序)大于新元素,将该元素移到下一位置
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  • 将新元素插入到该位置后
  • 重复步骤[2,5]

Python实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

def insert_sort(arr):
n = len(arr)
for i in range(1,n): # 默认第一个已经排好序
# 从后往前扫描
if arr[i] < arr[i-1]:
temp = arr[i]
index = i
for j in range(i-1,-1,-1): # range(i,-1,-1) ~ [i,0]
if temp < arr[j]: # 如果该元素大于新元素
arr[j+1] = arr[j] # 则将该元素移到下一位置
index = j # 记录下标
else:
break
arr[index] = temp
return arr

算法时间复杂度O(n^2)

希尔排序

1959年Shell发明,其实质是分组插入排序,也叫缩小增量排序。其工作原理是按一定步长 k0 把长序列划分成若干个子序列,然后分别进行插入排序,接着再按照更长的步长 k1 再度划分子序列并插入排序,直到步长为 kn 为1为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

def shell_sort(arr):
n = len(arr)
gap = n // 2
while gap > 0:
# 分组进行插入排序
for i in range(gap,n):
tmp = arr[i]
index = i
while index - gap >= 0 and tmp < arr[index - gap] :
arr[index]= arr[index-gap]
index = index - gap
arr[index] = tmp
gap = gap // 2
return arr

希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)

快速排序

面试常考题。。

快速排序的基本思想:通过一趟排序把整个待排记录分为两个独立的部分,其中一部分的值都比另一部分小,然后递归地处理各自独立的部分,直到整个序列有序。

算法步骤:

  • 从数组中选择一个数作为“基准”( pivot )
  • 重新排序数列,所有比 pivot 小的数字在前面,比 pivot 大的数字在后面,等于 pivot 则在哪边都可以。
  • 递归地( recursive )把小于基准值元素的子数列和大于基准值元素的子数列排序

算法实现:

1
2
3
4
5
6
7
8
9

def quick_sort(arr):
if len(arr) <= 1: # 递归出口
return arr
else:
pivot = arr[0]
less = [ x for x in arr[1:] if x <= pivot ] # 小于或者等于 pivot 则放在左边
right = [x for x in arr[1:] if x > pivot ] # 大于则放在右边
return quick_sort(less) + [pivot] + quick_sort(right) # 递归调用

算法平均时间复杂度O(nlogn)

归并排序

算法采用分治法:将已有序的子序列合并,得到完全有序的序列。

合并两个有序数组的方法:比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可

基本思路是把数组分为 left 和 right ,如果这两个数组内部是有序的,那我们就可以按照合并两个有序数组的方法来获得一个排序好的数组。现在我们可以对子数组进行二分,直到细到一个元素为一个组为止,这个时候我们可以认为这个组是有序的,然后我们合并相邻数组即可。

算法步骤:

  • 把长度为 n 的输入序列切分两个长为 n/2 的子序列
  • 分别对子序列采用归并排序
  • 将两个已经排序好的子序列合并成一个排序好的输出序列
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


def merge_arr(left,right):
"""
有序地合并两个数组
"""
l,r = 0 , 0 # left 和 right 数组的下标指针
res = [] # 初始化结果数组
while l < len(left) and r < len(right):
if left[l] < right[r]:
res.append(left[l])
#print(left[l])
l += 1
else:
res.append(right[r])
#print(rifht[r])
r += 1

res += left[l:]
res += right[r:]
return res

def merge_sort(arr):
"""
递归地合并数组
"""
if len(arr) <= 1 :
return arr
else:

num = len(arr) // 2
left = merge_sort(arr[:num])
right = merge_sort(arr[num:])
return merge_arr(left,right)

算法复杂度 O(nlogn)

堆排序

pass

计数排序

计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

算法步骤:

  • 找出待排序数组中最大和最小的元素
  • 统计数组中每个值为 i 的元素出现的次数,存到数组 C 的第 i 项
  • 对所有的计数累加(从 C 中的第一个元素开始,每一项和前一项相加)
  • 反向填充目标数组,将每个元素i放在新数组的第 C(i) 项,每放一个元素就将C(i)减去1

权当有趣看看

参考文章

  1. 十大经典排序算法
  2. 经典排序算法总结与实现
  3. 维基百科 - 希尔排序
  4. 快速排序的时间复杂度

Linux--sort&uniq

Posted on 2019-08-26

今天工作使用到对一个超过100万行的文件,去重统计某一列有多少个。使用命令如下

1
cat xx.csv | sort -t ',' -k 1,1 -u | wc -l

其中-t表示每行的分隔符是什么,-k表示统计第几列到第几列,-u表示去重

wc -l 统计文件有多少行

sort 和 uniq 这两个命令还有挺多东西需要学习一下的,后面整理一下。


2019-08-26

在工作的场景中,我们经常针对大文本做一些统计,比如说前文的去重统计某一列的id个数。使用 Python 统计固然可以,但是我们也有更方便的办法:sort and uniq 。

我们先创建一个 demo.txt 的文件,来做实验:

1
2
3
4
5
6
7
$cat demo.txt
1,2,3
2,3,3
2,3,4
2,3,3
5,5,2
4,2,3

sort : 为排序而生

sort 基本用法:

1
2
cat <filename> | sort
sort <file1> [<file2> [<file3> [...]]]

两个命令都可以, sort 会逐行读取内容,然后按照 ASCII 编码的顺序,升序排列文件的行,最后输出到标准输出,其中 sort 可以连续读入多个文件,然后合在一起排序。

1
2
3
4
5
6
7
$sort  demo.txt
1,2,3
2,3,3
2,3,3
2,3,4
4,2,3
5,5,2

sort 常用选项:

  • -o: 定向输出文件
  • -r: 倒叙排列
  • -n: 排序数字时使用
  • -u: 去除重复行
  • -t: 指定分隔符(通常与 -k 联用)
  • -k: 指定用于排序的列号
  • -f: 忽略大小写
  • -M: 能够排序月份
  • -b: 忽略行首空白
  • -u: 对有相同键的列去重

uniq 治重复专用

uniq 的作用可以描述为:针对相邻的重复行,进行相应的动作。(大哥讲太好,忍不住复制了过来)

这句话中,有两个地方需要注意。首先,针对的是相邻的重复行。因此,uniq 对于不相邻的重复行是不起作用的。其次,进行相应的动作。这意味着,uniq 可以做的事情很多,不止一样。

uniq 默认是对相邻的重复行,进行去重的操作:

1
2
3
4
5
6
7
$cat demo.txt | sort  | uniq
1,2,3
2,3,3
2,3,4
4,2,3
5,5,2
# 2,3,3进行了去重

上面命令也就很简单了:把 demo.txt 的文件按照升序排列,然后去除重复的行,最后输出到标准输出(屏幕)

当然,我们也可以给 uniq 加上不同的参数,来解锁不同的姿势。

1
2
3
4
5

cat <filename> | sort | uniq -d # 只显示重复的行,每行只显示一次
cat <filename> | sort | uniq -i # 忽略大小写
cat <filename> | sort | uniq -u # 只显示只出现一次的行
cat <filename> | sort | uniq -c # 统计每行重复的次数

使用sort 和 uniq

准备另一个集合 demo_cp.txt

1
2
3
4
5
6
$cat demo_cp.txt
1,2,3
2,3,3
2,3,4
2,3,3
6,1,2
求交集

我们想要知道两个文件里面都出现了的元素,那么就是两个文件重复的行,easy。

1
$sort <file1> <file2> | uniq -d # 交集

实战一下:

1
2
3
4
$sort demo.txt demo_cp.txt | uniq -d
1,2,3
2,3,3
2,3,4
求并集

前文已经演示过了,easy

求差集

也就是重合过的不要呗,那就是 uniq -u 统计只出现过一次的行

1
$sort <file1> <file2> | uniq -u # 差集

参考

感恩大佬的文章最佳搭档:利用 sort 和 uniq 做集合运算

星星与雪花

Posted on 2019-08-22

星型模型

中央表包含事实数据,多个表以中央表为中心呈放射状分布,它们通过数据库的主键和外键相互连接,是一种使用关系数据库实现多维分析空间的模式,其基本形式必须实现多维空间,以使用关系数据库的基本功能。同时星型模型也是一种非正规化的模型,多维数据集的每一个维度直接与事实表连接,没有渐变维度,所以存在冗余数据。在星型模型中,只需要扫描事实表就可以进行查询,主要的数据都在庞大的事实表中,所以查询效率较高,同时每个维度表和事实表关联,非常直观,很容易组合出各种查询。

特点:

  • 只有一个事实表
  • 事实表中的每一个元组都是一个外键指向维度表的主键。
  • 维度表的信息组成这个维度的全部信息
  • 事实表与维度表通过主键外键相关联,维度表之间没有关联,就像很多星星围绕在一个恒星周围,故取名为星形模型

优点:

  • 模型简单
  • 查询效率较高

雪花模型

雪花模型在星型模型的基础上,维度表进一步规范化为子维度表,这些子维度表没有直接与事实表连接,而是通过其他维度表连接到事实表上,看起来就像一片雪花,故称雪花模型。也就是说雪花模型是星型模型的进一步扩展,将其维度表扩展为更小的维度表,形成一种层次。这样就通过最大限度的减少数据存储量以及联合较小的维度表来改善查询性能,且去除了星型模型中的冗余数据。

星座模型

星座模型其实是更复杂的星型模型:多张事实表,维度表公用。

这个其实更贴近于现实的使用情况。

看个图

星型模型和雪花模型
星星与雪花

事实表与维度表

Posted on 2019-08-22

事实表

事实表记录的是现实世界中一个最小的度量事件,比说「谁」在「什么时间」干了「什么事」。官方一点的表述则是:事实表(FactTable)是指存储有事实记录的表,包含了每个事件的具体要素,以及具体发生的事情,如系统日志、销售记录等。

事实表里面没有实际存放的内容,是一堆主键的集合,不同的主键可以关联到不同的维度表。

一般体积会很大, 增长会很快。

维度表

描述事实表中的具体信息。

它保存了维度的属性值,可以跟事实表做关联;相当于将事实表上经常重复出现的属性抽取、规范出来用一张表进行管理。
常见的维度表有:日期表(存储与日期对应的周、月、季度等的属性)、地点表(包含国家、省/州、城市等属性)等

看图说话

不过一般事实表都会加上时间戳,不然时间维表也会挺大的

实时表和维度表

数据仓库分层

Posted on 2019-08-22

为什么要分层

  • 减少重复开发:可以开发一些通用的中间层数据,大大减小开发量
  • 清晰的数据结构:每一个数据分层都有它的作用域,使用的时候方便我们更好地定位和理解
  • 数据血缘追踪:如果说出了问题,可以更快地知道是哪里出了问题,并且知道其危害的范围
  • 把复杂任务简单化:把复杂任务拆分成多个简单任务来执行,每一步只干一个事儿。降低难度且易于追踪问题。
  • 使仓库更加稳健,屏蔽脏数据或者是数据的缺失等问题对后续任务的影响

常见分层方法

  • ods
  • dw
  • dm
ods

ODS(Operational Data Store)操作数据存储。

和源数据基本同构,不过是经过我们ETL处理的,是干净的数据,粒度最细。

dw

DW(Data Warehouse)是数据仓库最核心的一层。

在这一层按照各种主题建立数据模型,比如说星型,或者雪花模型。

dm

Dm(Data Market)存放的是轻度聚合的数据。

根据业务需求,生成面向业务人员可以直接使用的表,最典型的应用就是报表。报表应用时,为了响应速度更快,一般都会存至MySQL里面。

  • tmp层 : 用于计算中的临时表,可以抽象为一个临时层
  • dim层 : 维度表,是对事实表中事件的要素的描述信息

KNN原理与实现

Posted on 2019-08-21

KNN原理

k 近邻算法的输入为实例的特征向量,对应于特征空间的点;输出为实例的类别,可以取多类。k 近邻算法假设给定一个训练数据集,其中的实例类别已定。分类时,对新的实例,根据其 k 个最近邻的训练实例的类别,通过多数表决等方式进行预测。因此,k近邻算法不具有显式的学习过程。

简单来说就是:我们想要知道研究的对象属于什么分类,我们只需要知道距离这个对象最近的K个对象里面,哪一个分类占比最大,那么该对象就属于这个分类。

近朱者赤,近墨者黑

一般地距离计算公式:

实际使用

  • 收集数据:任何方法
  • 准备数据:距离计算所需要的数值,最好是结构化的数据格式
  • 分析数据:任何方法
  • 训练算法:此步骤不适用于 k-近邻算法
  • 测试算法:计算错误率
  • 使用算法:输入样本数据和结构化的输出结果,然后运行 k-近邻算法判断输入数据分类属于哪个分类,最后对计算出的分类执行后续处理

我们尽量把过程可视化,这样方便我们比较直观地区理解,代码使用 Jupyter Notebook 编辑。

step1 引入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from sklearn import datasets
import seaborn as sns
import pandas as pd
%matplotlib inline

# 导入数据,从 seaborn 中导入「鸢尾花」数据
dataset = sns.load_dataset('Iris')
# 因为有多组特征,但是这里是举例子,且为了可视化,我们取前两个特征
# DataFrame 获取特定列方法 df[['a','b']] | df.loc[:,['a',b]]
dataset2 = dataset[['species','sepal_length','sepal_width']]
sns.relplot(x='sepal_length',y='sepal_width',hue='species',data=dataset2)

输出如下:

图片还是有点复杂,随意取 setosa 和 versicolor

1
2
3
4
dataset3 = dataset.loc[:,['species','sepal_length','sepal_width']] 
# virginica先不看
dataset3 = dataset3[ dataset3['species'] != 'virginica']
sns.relplot(x='sepal_length',y='sepal_width',hue='species',data=dataset3)

输出如下:

step2 划分测试集和训练集

1
2
3
4
5
6
7
8
9
10
11
12
13
# 拆分数据,重新命令一下列名,查看一下测试集和训练集的分布
from sklearn.model_selection import train_test_split
# dataset3先放着吧
dataset4 = dataset3

# 划分测试集和训练集
species_train , species_test = train_test_split(dataset4['species'],test_size=0.2)

# 这里取个巧,因为数据量小,还是放到一起,比较好画图
dataset4['type'] = dataset4['species']
dataset4['type'][species_test.index] = 'test'

sns.relplot(x='sepal_length',y='sepal_width',hue='type',data=dataset3)

输出如下:

step3 训练数据

knn没有训练这一步

step4 预测数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# KNN其实灭有训练的过程, 计算即可,但是为了符合标准流程,试一试

from sklearn.neighbors import KNeighborsClassifier

data = dataset4 # 复制一份最终的数据

data_train = data[data['type']!='test']
data_test = data[data['type'] == 'test']

# 使用 sklearn knn算法模型进行训练和预测
knn = KNeighborsClassifier(n_neighbors=5)

#length_train = data[data['type'] == 'test']['sepal_length'].values
#width_train = data[data['type'] == 'test']['sepal_width'].values

knn.fit(data_train[['sepal_length','sepal_width']],data_train['species'])
data_test['type'] = knn.predict(data_test[['sepal_length','sepal_width']]) #这里灭有对齐

# 展示一下分类的结果,如下图所示,基本和 dataset3 一致
data_res = pd.concat([data_train,data_test],axis=0)
sns.relplot(x='sepal_length',y='sepal_width',hue='type',data=data_res)

输出如下:

可以看到,预测十分准确(主要是因为数据太干净太好了)

knn 简单实现

根据开头说的原理,我们可以给出一个很简单的实现,理解处理过程即可,细节灭有深究。
主要有三步:

  1. 计算新数据与样本数据集中每条数据的距离。
  2. 对求得的所有距离进行排序(从小到大,越小表示越相似)。
  3. 取前 k (k 一般小于等于 20 )个样本数据对应的分类标签。
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
import numpy as np
from collections import Counter

class myKnn:

def __init__(self,neighbor):
self.neighbor = neighbor
self.x = None
self.y = None
def fit(self,x,y):
# knn 并灭有训练过程, 所以简单地对样本赋值即可
self.x = x
self.y = y
def _predict(self,x):
'''
核心步骤,寻找单个对象所属分类
'''
# 计算欧式距离,如果点过多,则计算量会很大
distance = [sqrt(np.sum(i-x)**2) for i in self.x]
# 返回排序后的下标
sorted_distance = np.argsort(distance)
# 找到K个邻居
neighbor_index = sorted_distance[:self.neighbor]
neighbor_x = self.y[neighbor_index]
# 找到K个邻居中最大的标识
return Counter(neighbor_x).most_common(1)[0][0]
def predict(self,x_predict):
'''
多次调用单个对象的分类,即可实现多个样本的分类
'''
y_predict = [self._predict(x) for x in x_predict]
return np.array(y_predict)
def score(y_predict,y_test):
right = 0
length = len(y_test)
for i in range(length):
if y_predict[i] == y_test[i]:
right += 1
return right
def show_neighbor(self):
print(self.neighbor)

数据的归一化问题

假设我们的数据,一个特征是数字极大但另一个特征特别小,那么我们计算的时候,对象间的距离就会被数字大的特征影响,导致结果不准确。所以我们要使用归一化的方法来处理。
max-min 归一化:

$Z$归一化:

knn 调参

k 是最重要的一个参数,一般建议是 k 小于20,鉴于数据量小,我们可以暴力的遍历一下来看看不同 k 值的表现

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
# 参考 In[243] 代码 , 获取测试集和训练集

x_test = data_test[['sepal_length','sepal_width']]
y_test = data_test['species']

x = data[['sepal_length','sepal_width']]
y = data['species']

length = len(y_test)
y_tmp = np.array(y_test)

# 循环十次,找到最佳的K值
for i in range(1,10):
right = 0
# 初始化对象
knnme = myKnn(neighbor=i)
knnme.fit(x.values,y.values)
y_predict = knnme.predict(x_test.values)

# 计算正确率,以此来打分
for k in range(length):
if y_predict[k] == y_tmp[k]:
right += 1

print(i,right/length)

输出忽略,结论是 k = 3 或者 k = 5 时正确率最高。

当然了一个算法不会如此简单,也还有其他参数,比如说【距离的权重】等等,后面还得细细看

knn 的优缺点

优点:精度高、对异常值不敏感、无数据输入假定
缺点:计算复杂度高、空间复杂度高
适用数据范围:数值型和标称型

SIR模型

Posted on 2019-08-14

SIR模型是一种传播模型,是信息传播过程中抽象的表述。

模型简述

这是最经典的传染病模型之一,整个模型简洁有力。

  • $S$ - 易感染者( susceptibles )
  • $I$ - 已感染者( infectives )
  • $R$ - 已免疫者( recovered )

此外还有两个重要概率:

  • $\alpha$ - 感染概率(这个是重点,不同应用场景对此定义不同)
  • $\beta$ - 免疫概率

在基于以下假设的情况下(时间为$t$):

  1. 总人口$N$不变,即所有人都存在$ N(t) = S(t) + I(t) + R(t) $
  2. 如果已感染者与易感染者接触,则必定存在被感染的可能,即感染概率 $\alpha$
  3. 单位时间内,部分已感染者恢复成已免疫者,即免疫概率 $\beta$

在瞬时时刻$t$,我们就可以得到下面这个方程:

统计知识学习笔记-零

Posted on 2019-08-12

chapter1 随机事件与概率

chapter2 一维随机变量分布


chapter3 二维随机变量分布


chapter4 数字特征


chapter5 大数定律与中心极限定理

chapter6 数理统计

chapter7 估计与评价

chapter8 假设检验

MongoDB

Posted on 2019-08-10

MongoDB 是由 C++ 编写的文档类型非关系数据库,是一个基于分布式文件系统的开源数据库系统。其内容以 BSON 的形式存储(类似JSON对象),字段值非常灵活,可以是其他文档,数组及文档数组等。

非关系数据库

非关系型数据库也叫 NoSQL ,表示 not only sql。

2009年初,Johan Oskarsson 举办了一场关于开源分布式数据库的讨论,Eric Evans 在这次讨论中提出了 NoSQL 一词,用于指代那些非关系型的,分布式的,且一般不保证遵循ACID原则的数据存储系统。Eric Evans 使用 NoSQL 这个词,并不是因为字面上的“没有 SQL ”的意思,他只是觉得很多经典的关系型数据库名字都叫“ xxSQL ”,所以为了表示跟这些关系型数据库在定位上的截然不同,就是用了“ NoSQL “一词

非关系型数据库提出了「以键值对存储,不确定结构」这种灵活的存储方式,由此也带来了和关系型数据库很多的不同:

  • 模式自由 - 不需要定义表结构
  • 逆规范化 - 不遵循范式要求,去掉完整性约束,减少表之间的依赖
  • 弹性可拓展 - 可以动态地增删节点
  • 弱事务 - 不支持ACID特性,不过可以保证事务的最终一致性
  • 多副本异步复制 - 数据快速写入一个节点,其余节点通过读取写入的日志来实现异步复制

MongoDB

优点有很多,高可用高性能易拓展面向文档等

安装与启动

Mac OS 下安装

1
brew install mongodb

启动 mongodb

1
2
3
4
5
6
#进入到安装目录
cd <mongodb installation dir>
# 开启进程
./bin/mongod
#开启shell客户端
./bin/mongo

基本概念

参考关系型数据库( RDBMS )的概念来理解,详情可以参见SQL到MongoDB映射表

MySQL MongoDB
database database
table collection
row document
column field
index index
primary key primary key

开始操作吧

创建数据库和集合
1
2
3
4
5
6
> use test
switched to db test
> db
test
> db.createCollection("users")
{ "ok" : 1 }
插入数据

db.collection.insertOne() - 插入单条数据
db.collection.insertMany() - 插入多条数据

实践一下

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
# 插入单条
> user.insertOne({"name":"zhangyang","email":"zy@123.com"})
{
"acknowledged" : true,
"insertedId" : ObjectId("5d4f0623fc2a443cf5808e28")
}

# 插入多条
> info = [{"name":"yang","email":"yang@123.com"},{"name":"peikun","email":"pk@123.com"}]
[
{
"name" : "yang",
"email" : "yang@123.com"
},
{
"name" : "peikun",
"email" : "pk@123.com"
}
]
> user.insertMany(info)
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("5d4f06ebfc2a443cf5808e29"),
ObjectId("5d4f06ebfc2a443cf5808e2a")
]
}
>

查询操作

db.collection.find() - 读取文档的方法,官方文档讲得很细

1
2
3
4
5
6
7
8
9
10
11
# 指定字段查询
> user.find({"name":"yang"})
{ "_id" : ObjectId("5d4f06ebfc2a443cf5808e29"), "name" : "yang", "email" : "yang@123.com" }
# 相当于 select * from user limit 2
> user.find().limit(2)
{ "_id" : ObjectId("5d4f0623fc2a443cf5808e28"), "name" : "zhangyang", "email" : "zy@123.com" }
{ "_id" : ObjectId("5d4f06ebfc2a443cf5808e29"), "name" : "yang", "email" : "yang@123.com" }
# in 条件限制
> user.find({"name":{"$in":["peikun","yang"]}})
{ "_id" : ObjectId("5d4f06ebfc2a443cf5808e29"), "name" : "yang", "email" : "yang@123.com" }
{ "_id" : ObjectId("5d4f06ebfc2a443cf5808e2a"), "name" : "peikun", "email" : "pk@123.com" }

这里要附上比较符号表

符号 含义
$lt 小于
$gt 大于
$ne 不等于
$lte 小于等于
$gte 大于等于
$in 包括
$nin 不包括

还有通过正则表达式的方式查询

1
2
3
> user.find({"name":{"$regex":"yang$"}})
{ "_id" : ObjectId("5d4f0623fc2a443cf5808e28"), "name" : "zhangyang", "email" : "zy@123.com" }
{ "_id" : ObjectId("5d4f06ebfc2a443cf5808e29"), "name" : "yang", "email" : "yang@123.com" }

更新操作

db.collection.updateOne() - 即使可能有多个文档通过过滤条件匹配到,但是也最多也只更新一个文档。
db.collection.updateMany() - 更新所有通过过滤条件匹配到的文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 先查一下 原始数据
> condition = {"name":"zhangyang"}
{ "name" : "zhangyang" }
> user.find(condition).pretty()
{
"_id" : ObjectId("5d4f0623fc2a443cf5808e28"),
"name" : "zhangyang",
"email" : "zy@123.com"
}

# 调用 update() 方法
> user.updateOne(condition,{"$set":{"email":"zhangyang@123.com"}})
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> user.find(condition).pretty()
{
"_id" : ObjectId("5d4f0623fc2a443cf5808e28"),
"name" : "zhangyang",
"email" : "zhangyang@123.com"
}
>
删除文档

db.collection.remove() - 删除符合条件的所有
db.collection.deleteOne() - 即使可能有多个文档通过过滤条件匹配到,但是也最多也只删除一个文档。
db.collection.deleteMany() - 删除所有匹配指定过滤条件的文档。

1
2
3
4
5
6
7
8
9
10
11
12
> condition = {"name":"zhangyang"}
{ "name" : "zhangyang" }
> user.find(condition).pretty()
{
"_id" : ObjectId("5d4f0623fc2a443cf5808e28"),
"name" : "zhangyang",
"email" : "zhangyang@123.com"
}
> user.remove(condition)
WriteResult({ "nRemoved" : 1 })
> user.find(condition)
> # 此时并没有输出
创建索引

db.collection.createIndex() - 使用B树数据结构

参考

感恩为以下内容付出努力的人!

  1. 非关系型数据库(nosql)介绍
  2. MongoDB官网
  3. MongoDB中文文档
  4. MongoDB中文社区
  5. Python操作MongoDB
  6. MongoDB教程

Pymysql简单使用

Posted on 2019-08-10

写代码的时候,操作数据库是必须的。之前都是能用就可以,今天得好好总结一下,这里用 pymysql 举例。

什么是Pymysql

纯 Python 实现的一个 MySQL 客户端协议,兼容几乎所有的 MySQL 数据库。

简单使用

安装
1
pip install pymysql
具体例子

先来看看官网给我们的例子,插入&查询一条记录

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
import pymysql

# 建表
'''
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(255) COLLATE utf8_bin NOT NULL,
`password` varchar(255) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
AUTO_INCREMENT=1 ;
'''

# connecition 信息
connection = pymysql.connect(host = 'localhost',
user = 'root',
password = 'xxx',
db = 'demo',
charset = 'utf8mb4',
cursorclass=pymysql.cursors.DictCursor
)
try:
# 使用 with 的好处在于 with 结束后会自动关闭 cursor
# 插入数据
with connection.cursor() as cursor:
sql = "insert into users (`email`,`password`) values (%s,%s)"
cursor.execute(sql,('zy@xx.com','123456'))
# 连接默认不开启 autocommit,所以需要我们自己去 commit
connection.commit()

# 查询数据
with connection.cursor() as cursor:
sql = 'select `id` , `email` , `password` from `users` where `email` = %s '
cursor.execute(sql,('zy@xx.com'))
res = cursor.fetchone() # 这里已经提交了
print(res)
finally:
connection.close()

输出则是:(1, 'zy@xx.com', '123456')

连接对象
1
pymysql.connections.Connection(host=None, user=None, password='', database=None, port=0, unix_socket=None, charset='', sql_mode=None, read_default_file=None, conv=None, use_unicode=None, client_flag=0, cursorclass=<class 'pymysql.cursors.Cursor'>, init_command=None, connect_timeout=10, ssl=None, read_default_group=None, compress=None, named_pipe=None, autocommit=False, db=None, passwd=None, local_infile=False, max_allowed_packet=16777216, defer_connect=False, auth_plugin_map=None, read_timeout=None, write_timeout=None, bind_address=None, binary_prefix=False, program_name=None, server_public_key=None)

初始化 connection 对象时有很多参数,这里我们说几个比较常用的,具体信息可以参考官方文档

  • host - 数据库服务器地址
  • user - 用户名
  • password ( passwd ) - 密码,默认为空字符串
  • database ( db ) - 操作的数据库
  • port - MySQL使用的端口,默认3306
  • charset - 想要使用的数据库编码
  • cursorclass - 默认的游标类型
  • connect_timeout - 连接超时时间
  • autocommit - 是否自动提交,默认为否
  • local_infile - 是否允许可以load本地数据

一般我们使用 cursor() 和 commit() 方法

获取游标

cursor=connection.cursor(),有了 cursor 对象之后我们才可以进行各种增删改查等数据库操作,其对我们最重要的方法是 execute() 和 fetch() 方法,因为我们所有对数据的操作都是通过他们执行的。size 在 fetchmany()

cursor 可以通过创建时指定类型,有

  • Cursor - 元组类型
  • SSCursor - 无缓冲元组类型
  • DictCursor - 字典类型
  • SSDictCursor - 无缓冲字典类型
增删改查
查询数据

还是之前的例子

1
2
3
4
with connection.cursor() as cursor:
sql = 'select `id` , `email` , `password` from `users` where `email` = %s '
cursor.execute(sql,('zy@xx.com'))
res = cursor.fetchone()

不过还有几个其他的方法:

  • fetchall() - 取回全部数据
  • fetchmany(size) - 取回一部分数据,由 size 决定

两者都返回一个 list 对象,不过我以为会是一个生成器的…

增删改数据

使用 cursor.execute(),方法,这个写好 sql 就好,不过这些操作都需要 cursor.commit() 操作才会生效。还有一个 cursor.executemany() 的方法,使用极少极少。

参考

  1. PyMySQL-PyPi
  2. PyMySQL官方文档
1…345…12
zhangyang

zhangyang

120 posts
39 tags
© 2022 zhangyang
Powered by Hexo
|
Theme — NexT.Mist v5.1.4