Numpy 学习笔记

Numpy 中数据结构的核心是 n 维数组 n-dimensional array,简称 ndarray,需要注意的是数组中的元素都要属于同一个数据类型。

几个常用的 ndarray 的属性:ndarray.dtype,ndarray.shape,ndarray.ndim

几个常用的生成 ndarray 的方法:np.zeros( ),np.ones( ),np.zeros_like( ),np.ones_like( ),np.empty( ),np.arange( ),np.eye( ),np.identity( )

np.zeros(shape, dtype=float):shape 参数可以是一个自然数,也可以是二维元组,当元组中有三个参数时,后两个参数可以理解为 ndarray 的行数和列数,第一个参数为同样形状的 array 的个数。继续增加参数的个数的工作机制同上,都是对于后续的数组进行一个按照给定参数的量进行复制,np.ones( ) 工作机制类似。

np.zeros((2, 5, 3))
array([[[ 0.,  0.,  0.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.]],

       [[ 0.,  0.,  0.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.],
        [ 0.,  0.,  0.]]])

np.zeros_like()np.ones_like() 的参数为已有的一个 ndarray,会返回一个同形状的元素全 0 或全 1 的 ndarray。

np.empty() 生成一个占位的空数组,由于需要自行设置数组中的每一个值,因此要尽量少用。

np.arange() 的参数为一个整数,工作机制类似于 python 内建函数 range,但返回一个 ndarray。

np.arange(11)
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10])

np.eye(N, M=none, k=0, dtype=<type 'float'>) :N 为矩阵的行,M 为矩阵的列,如果 M 不指定则默认为 N x N 方阵,k 为 1 所在的对角线的位置

不指定列数:

np.eye(3) # 相当于 np.identity(3)
array([[ 1.,  0.,  0.],
       [ 0.,  1.,  0.],
       [ 0.,  0.,  1.]])

指定列数和 1 所在对角线位置:

np.eye(3, 4, 2)
array([[ 0.,  0.,  1.,  0.],
       [ 0.,  0.,  0.,  1.],
       [ 0.,  0.,  0.,  0.]])

当 k ≥ M 时,为 0 矩阵:

np.eye(3, 4, 4)
array([[ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.]])

还可以通过 np.identity() 来创建单位矩阵

np.identity(4)
array([[ 1.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.],
       [ 0.,  0.,  1.,  0.],
       [ 0.,  0.,  0.,  1.]])

ndarray 的索引和切片

一维 ndarray

一维 ndarray 的索引和切片与 list 类似,主要差别是可以通过索引和切片直接修改原 ndarray 中的值。同时, ndarray 的切片只是提供了原 ndarray 的一个局部视图 view 而不是原 ndarray 的一个拷贝,但对于切片的任何修改都会反映到原有的 ndarray 中去。后续大量数组的操作都是采用视图而非拷贝的原因是 Numpy 是为了操作大量数据而构建的,因此从性能的角度出发需要尽量的减少内存占用。

arr = np.arange(10)
arr[3: 8] = 10
arr_slice = arr[5: 8]
arr_slice
array([10, 10, 10])

对切片进行索引赋值操作会直接作用到原来的数组:

arr_slice[1] = 12
arr
array([0, 1, 2, 10, 10, 10, 12, 10, 8, 9])

如果确实需要进行复制,则可以通过显式的使用 arr[ 5: 8].copy() 来完成。

当对一维数组进行 arr.shape 查询时结果是诸如 (3, ) 的形式,这个看起来很怪异的形状在 Numpy 中称为 Rank 1 数组,对应的 arr.ndim 返回 1,在实际使用中应尽量避免这种数据结构,原因请参见这篇 rank 1 数组

二维和高维 ndarray

二维数组的单个索引值得到的是一个一维数组:

arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
arr2d[2]
array([7, 8, 9])

两个索引值则会获取相应位置的值:

arr2d[2, 2] # arr2d[2][2]
9

高维数组的索引机制与二维类似,即会根据提供的索引值逐次找到相应位置的数组或元素。

arr3d = np.array([[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]])
arr3d.shape
(1, 4, 3)

单个索引值:

arr3d[0] # arr3d[1] will pop out an error
array([[ 1,  2,  3],
       [ 4,  5,  6],
       [ 7,  8,  9],
       [10, 11, 12]])

两个索引值:

arr3d[0, 1] # shape = (3, ) 
array([4, 5, 6]) 

通过索引修改数组中的值:

arr3d[0, 1] = 1
arr3d
array([[[ 1,  2,  3],
        [ 1,  1,  1],
        [ 7,  8,  9],
        [10, 11, 12]]])

也可以通过切片的形式访问数组中的元素:

arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
arr2d[2, :2]
array([7, 8])

上面已经简单提到 Rank 1 数组这个对象,在实际应用中经常需要通过切片方式来获得数组的一个行或列中的数值并用于后续计算,而由下图可知由于这些数值可以通过采用不同的切片方式获得,此时为了避免产生 Rank 1 数组,要尽量使用双冒号切片形式进行选取。

Array slicing

布尔值索引

通过布尔值索引是一个非常重要的特征,可以通过这个操作快速选择满足一定特征的元素:

names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7, 4)
data
array([[ 0.91937812,  0.37117872, -0.24042518,  0.30905519],
       [-0.49121926, -1.0682015 , -0.0988304 ,  1.43795767],
       [ 1.11437469,  1.19878961,  0.00452662,  1.15337206],
       [-1.1719382 , -0.13256179, -0.61995845, -0.99759886],
       [-0.50507224, -2.60465047, -1.4235047 ,  0.83859547],
       [-1.26945164, -0.07747245,  1.50020684, -0.55767208],
       [ 1.9902588 , -1.35701671,  1.28836883,  0.33033936]])

在数组索引中提供单个索引值时是针对行进行索引的,所以这里作为判断条件的 names 的行数必须和 data 的行数相等。

data[names == 'Bob']
array([[ 0.91937812,  0.37117872, -0.24042518,  0.30905519],
       [-1.1719382 , -0.13256179, -0.61995845, -0.99759886]])

布尔值索引还可以和切片联合使用:

data[names == 'Bob', 2:]
array([[-0.24042518,  0.30905519],
       [-0.61995845, -0.99759886]])

布尔值索引也可以同时用于修改符合某些条件量的值:

data[data < 0] = 0
data
array([[ 0.91937812,  0.37117872,  0.        ,  0.30905519],
       [ 0.        ,  0.        ,  0.        ,  1.43795767],
       [ 1.11437469,  1.19878961,  0.00452662,  1.15337206],
       [ 0.        ,  0.        ,  0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        ,  0.83859547],
       [ 0.        ,  0.        ,  1.50020684,  0.        ],
       [ 1.9902588 ,  0.        ,  1.28836883,  0.33033936]])

需要注意的是:

  • 通过布尔值索引进行数值选择时会返回一个符合判断条件的数值的拷贝,而如果在选择的同时给予赋值则在原地进行

  • 逻辑运算符可以采用 & (and) 和 | (or),但不能直接使用 andor

花式索引 Fancy indexing

花式索引在 Numpy 中是指用一个整数型数组作为索引值来对数组进行索引,并且按照指定的顺序以复制的方式返回索引值。

arr = np.empty((8, 4))
for i in range(8):
    arr[i] = i
    
arr
array([[ 0.,  0.,  0.,  0.],
       [ 1.,  1.,  1.,  1.],
       [ 2.,  2.,  2.,  2.],
       [ 3.,  3.,  3.,  3.],
       [ 4.,  4.,  4.,  4.],
       [ 5.,  5.,  5.,  5.],
       [ 6.,  6.,  6.,  6.],
       [ 7.,  7.,  7.,  7.]])
arr[[4, 3, 0, 6]]
array([[ 4.,  4.,  4.,  4.],
       [ 3.,  3.,  3.,  3.],
       [ 0.,  0.,  0.,  0.],
       [ 6.,  6.,  6.,  6.]])

当在花式索引中提供两个整数数组时,返回的是被索引数组中以两个数组对应为元素为坐标的元素值:

arr = np.arange(32).reshape((8, 4))
arr
array([[ 0,  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]])
arr[[1, 5, 7, 2], [0, 3, 1, 2]] 
array([ 4, 23, 29, 10])

而如果想返回一个区域的值,则需要按照如下的方式先进行行索引,再对列进行排序:

arr[[1, 5, 7, 2]][:, [0, 3, 1, 2]] 
array([[ 4,  7,  5,  6],
       [20, 23, 21, 22],
       [28, 31, 29, 30],
       [ 8, 11,  9, 10]])

等同于 arr[np.ix_([1, 5, 7, 2], [0, 3, 1, 2])],后续 [0, 3, 1, 2] 为对列进行重新排序。

数组的转置和轴的交换

二维数组的转置可以理解为矩阵的转置,并且还可以在转置的基础上做向量的点积运算。

arr = np.arange(15).reshape((3, 5))
arr
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
arr.T # arr.transpose() 的一个特殊应用形式
array([[ 0,  5, 10],
       [ 1,  6, 11],
       [ 2,  7, 12],
       [ 3,  8, 13],
       [ 4,  9, 14]])

下面这个运算的结果是一个实对称矩阵,每一个对角线元素等于对应位置的行列元素的平方和,并且转置提供的依然是原有数组的一个视图,不改变原有数组的形状。

np.dot(arr.T, arr) 
array([[125, 140, 155, 170, 185],
       [140, 158, 176, 194, 212],
       [155, 176, 197, 218, 239],
       [170, 194, 218, 242, 266],
       [185, 212, 239, 266, 293]])

当对高维数组施加 transpose( ) 时,其变换过程需要稍微做一些解释:对于一个数组来说,除了维数外,Numpy 还给它分配了轴,轴的序号按照各个轴在数组的 shape 元组中的索引位置来分配。针对下面这个例子来说,轴与形状的关系为 (2[ axis 0 ], 3[ axis 1 ], 4[ axis 2 ])。

arr = np.arange(24).reshape((2, 3, 4))
arr
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]],

       [[12, 13, 14, 15],
        [16, 17, 18, 19],
        [20, 21, 22, 23]]])

当按照如下所示的轴的顺序施加转置时,即将原来数组的 0 轴 和 1 轴交换,数组中的每一个元素都需要交换前两个坐标,也即转置后的形状将变为 (3, 2, 4)。例如 12 这个元素原来的索引坐标为 [1, 0, 0],在转置后交换其前两个索引坐标 [0, 1, 0]。

arr.transpose((1, 0, 2))
array([[[ 0,  1,  2,  3],
        [12, 13, 14, 15]],

       [[ 4,  5,  6,  7],
        [16, 17, 18, 19]],

       [[ 8,  9, 10, 11],
        [20, 21, 22, 23]]])

类似的可以实现转置的操作还有一个方法 swapaxes( ),其参数为两个需要交换的轴,所以下面这个操作和上面的 arr.transpose((1, 0, 2))一致。

arr.swapaxes(0, 1)
array([[[ 0,  1,  2,  3],
        [12, 13, 14, 15]],

       [[ 4,  5,  6,  7],
        [16, 17, 18, 19]],

       [[ 8,  9, 10, 11],
        [20, 21, 22, 23]]])

Universal Functions: 数组的快速元素级运算

Numpy 的强大之处就在于对于数值型变量的向量化 vectorization,避免了数值操作过程中大量的使用显式的 for 循环,universal functions 提供了一系列的元素级的操作,如:

  • np.square(some_array) 返回一个数组每一个元素的平方

  • np.maximum(array_A, array_B) 依次比较两个数组相同位置的元素并返回其中的最大值

  • np.where(condition, array_A, array_B) 相比 maximum 则可以进一步的返回符合某个判断条件的选择,判断逻辑为当条件满足时取 array_A,否则取 array_B。

arr_x = np.array([1.1, 1.2, 1.3, 1.4, 1.5])
arr_y = np.array([2.1, 2.2, 2.3, 2.4, 2.5])
cond = np.array([True, False, True, True, False])
np.where(cond, arr_x, arr_y) # 非向量化的实现方式为 [(x if c else y) for x, y, c in zip(arr_x, arr_y, cond)]
array([ 1.1,  2.2,  1.3,  1.4,  2.5])

np.where( ) 的参数中,第 2 个和第 3 个参数也可以是标量,常用于根据某个条件修改已有的数组中的值:

arr = np.random.randn(4, 4)
arr
array([[-1.22678789, -0.6000085 , -0.22466607, -0.85761133],
       [ 0.75063805,  1.40825566, -0.72430125, -0.12158849],
       [-0.83472573,  0.09379071, -0.1910247 , -2.38272863],
       [-1.51568709, -0.65348116, -1.22607901, -1.76620207]])
np.where(arr>0, 2, -2)
array([[-2, -2, -2, -2],
       [ 2,  2, -2, -2],
       [-2,  2, -2, -2],
       [-2, -2, -2, -2]])

np.where() 本身也可以做为备选参数以完成更加复杂的判断:

np.where(cond1 & cond2, 0, np.where(cond1, 1, np.where(cond2, 2, 3)))

数学和统计学计算

前面关于转置的部分简单提到了 Numpy 为数组分配了“轴”,这个概念的演示在很多 Numpy 的教程中都是以一些随机数或简单数值的数组来举例,使得理解这个概念的定义和意义变得困难。

事实上设定轴的现实意义在于实际应用中的数据都代表对于某些研究对象的特征的描述:在常用的单张二维数据表中一般单独的一行记录的是对于一个对象的不同特征的描述,而单独的一列则对应于同一个特征在不同对象中的记录。在二维数据表的基础上可以叠加多张数据表,这种组织形式的数据可以在后续的计算中使得数据可以沿某一个方向进行统计(例如计算加和,均值,方差等),由于这些统计的结果会在相应的方向上压缩相应的轴的长度,因此这些统计过程也常被称为缩减操作 Reduction operation。

在 Numpy 中 shape 和 axis 的关系是 shape 元组中的元素的索引位置就是相应的轴的名称,而shape 元组中的数值代表数组在相应轴上的数据的个数。一个数组的轴的数量 number of dimensions 称为维数,也称为秩 rank,这个 rank 可以通过数组中元素的分组形式来获得,也即 [ ] 的嵌套层数,或者通过 array.ndim 获得。注意这个定义和线性代数中的秩的定义是不同的,后者的查询方式为 np.linalg.matrix_rank(arr)。

关于 Numpy 轴的定义及计算可以参考 Lstyle 的这个笔记 Numpy 小记——有关 axis/axes 的理解,讲的非常好。个人认为更为直观的理解轴上的加总计算的方式就是可以想象这些数据沿着轴被串在一起的数字被汇总压缩为一个新的数字,因此在汇总后生成的新的数组的维数就会被降低。在应用中如果想保持原有的维数,可以使用关键字参数 keepdimes = True

具体来说,在使用 ndarray.sum( ),ndarry.mean( ) 的时候,如果不指定轴,则是对整个数组内的所有数值进行加总和平均,而如果指定相应的轴,则会沿着轴的方向进行。

b = np.array([[[1,2,3,4],[5,6,7,8]],[[2,4,6,8],[3,5,7,9]],[[2,2,8,8],[1,0,5,8]]])
b # shape = (3, 2, 4)
array([[[1, 2, 3, 4],
        [5, 6, 7, 8]],

       [[2, 4, 6, 8],
        [3, 5, 7, 9]],

       [[2, 2, 8, 8],
        [1, 0, 5, 8]]])
b.mean()
4.75
b.sum()
114
b.sum(axis=0) # shape = (2, 4)
array([[ 5,  8, 17, 20],
       [ 9, 11, 19, 25]])
b.sum(axis=1) # shape = (3, 4)
array([[ 6,  8, 10, 12],
       [ 5,  9, 13, 17],
       [ 3,  2, 13, 16]])
b.sum(axis=2) # shape = (3, 2)
array([[10, 26],
       [20, 24],
       [20, 14]])

元素的唯一性和集合

对于一维数组,Numpy 还设有 numpy.unique( ) 这个方法,返回一个数组中的元素的集合,即无重复的返回数组中排序过的元素。

names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
np.unique(names)
array(['Bob', 'Joe', 'Will'], dtype='<U4')

数组文件的读写

读写专门的 Numpy 文件:

  • 写入硬盘:np.save('filename.npy', array_to_be_saved)

  • 文件读入:np.load('filename.npy')

读写 txt 文件:

  • 写入硬盘:np.savetxt('filename.txt', array_to_be_saved)

  • 文件读入:np.loadtxt('filename.txt', delimiter=',')

线性代数相关操作

ndarray 的运算

我们一般很少直接的将 m 行 n 列的二维数组直接称为矩阵是因为这容易将其运算与矩阵运算混淆:在矩阵乘法中,m x n 的矩阵必须和 n x k 的矩阵才能进行。但在 ndarray 中这个要求则更加宽松:对于任意两个同形状的 n 维数组,其加 +,减 -,乘 *,除 / 运算都是基于相同位置的元素进行的,而标量和 ndarray 的运算是标量与每个元素进行相应的运算。

arr = np.array([[1, 2, 3], [4, 5, 6]])
arr * arr # the same as np.multiply(arr, arr)
array([[ 1,  4,  9],
       [16, 25, 36]])

前面已经讲到在 Numpy 中,两个同形的数组之间的乘法发生在相同位置的两个元素之间。对应的矩阵相乘则需要借助下文的点积方法 np.dot(a, b)。除点积外,numpy.linalg 模块下集成了很多线性代数的操作,如矩阵求逆,QR 分解,特征值特征向量求解,SVD 分解等。

X = np.random.randn(4, 4)
mat = X.T.dot(X)
mat
array([[ 3.72779983, -1.3869418 , -0.93343812, -2.25143467],
       [-1.3869418 ,  5.29047179,  0.18681953, -2.91567006],
       [-0.93343812,  0.18681953,  0.43223972,  0.40102812],
       [-2.25143467, -2.91567006,  0.40102812,  4.82265242]])
np.linalg.inv(mat)
array([[ 21.4056399 ,  13.58072833,  25.42913277,  16.08918057],
       [ 13.58072833,   8.94242629,  15.78239186,  10.43411384],
       [ 25.42913277,  15.78239186,  33.09360224,  18.66126891],
       [ 16.08918057,  10.43411384,  18.66126891,  12.47497716]])
mat.dot(np.linalg.inv(mat))
array([[  1.00000000e+00,  -3.55271368e-15,   0.00000000e+00,
          1.42108547e-14],
       [ -2.84217094e-14,   1.00000000e+00,   7.10542736e-15,
         -7.10542736e-15],
       [ -8.88178420e-16,   8.88178420e-16,   1.00000000e+00,
         -2.66453526e-15],
       [  0.00000000e+00,   7.10542736e-15,   0.00000000e+00,
          1.00000000e+00]])
q, r = np.linalg.qr(mat)
r
array([[-4.66480052,  1.31147337,  1.08153305,  3.34017604],
       [ 0.        , -6.06042658,  0.03694935,  5.06062672],
       [ 0.        ,  0.        , -0.28772209,  0.4701318 ],
       [ 0.        ,  0.        ,  0.        ,  0.03387203]])

Numpy 中的矩阵乘法

为了更清楚的表示输入输出,后续我会更改一下显示方式,回归原始的 Jupyter Notebook 中的状态。

import numpy as np

In [2]:
a = np.array([[1, 2, 3, 4],[5, 6, 7, 8]])
a

Out[2]:
array([[1, 2, 3, 4],
       [5, 6, 7, 8]])

In [3]:
b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [11, 12, 13]])
b

Out[3]:
array([[ 1,  2,  3],
       [ 4,  5,  6],
       [ 7,  8,  9],
       [11, 12, 13]])

Numpy 的矩阵乘法可以通过 np.dot(a, b) 或者 np.matmul(a, b) 来完成,当两个矩阵都是 2d array 时,结果相同的,而对于高维数组必须用 np.matmul(a, b) 来完成。

In [4]:
c = np.matmul(a, b)
c

Out[4]:
array([[ 74,  84,  94],
       [166, 192, 218]])

In [5]:
np.dot(a,b)
Out[5]:
array([[ 74,  84,  94],
       [166, 192, 218]])

需要注意的是, 对于一维 Numpy 数组来说,默认是按照行向量来存储的。如果需要通过其来构建列向量的时候,仅给予转置 arr.T 得到的还是行向量,需要采用 arr[ :, None]arr.reshape((n, 1)) 来实现:

import numpy as np
In [2]:
a = np.array([1, 2, 3, 4]) # 1 dimension array
a
Out[2]:
array([1, 2, 3, 4])

In [3]:
a.T # Transpose does not change the shape of 1 dimension array
Out[3]:
array([1, 2, 3, 4])

In [4]:
a.reshape(4, 1) # Reshape is the right way to do
Out[4]:
array([[1],
       [2],
       [3],
       [4]])

In [5]:
a[:, None] # or this one
Out[5]:
array([[1],
       [2],
       [3],
       [4]])
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335