Quantcast
Channel: CSDN博客推荐文章
Viewing all 35570 articles
Browse latest View live

Object Detection系列(四) Faster R-CNN

$
0
0

这里写图片描述
Object Detection系列(一) R-CNN
Object Detection系列(二) SPP-Net
Object Detection系列(三) Fast R-CNN
Object Detection系列(四) Faster R-CNN

Faster R-CNN简介

RGB团队在2015年,与Fast R-CNN同年推出了Faster R-CNN,我们先从头回顾下Object Detection任务中各个网络的发展,首先R-CNN用分类+bounding box解决了目标检测问题,SPP-Net解决了卷积共享计算问题,Fast R-CNN解决了end-to-end训练的问题,那么最后还能下一个ss算法,依旧独立于网络,是一个单独的部分,然而这个算法需要大概2秒的时间,这个点是R-CNN系列的性能瓶颈,所有Fast R-CNN是没有什么实时性的。那么Faster R-CNN的出现就是为了解决这个瓶颈问题。
在Faster R-CNN中提出了RPN网络,Region Proposal Network(区域建议网络)以代替原来的ss算法,可以简单的理解为:

Faster R-CNN =Fast R-CNN+RPN-ss算法

所以,可以说除了RPN,Faster R-CNN剩下的地方与Fast R-CNN是一样的, 那么理解Faster R-CNN的关键其实理解RPN。

RPN网络结构

这里写图片描述
首先,上面这张图说明了RPN在Faster R-CNN中的位置,它在CNN卷积的特征图上做区域建议(大约300个),并根据RPN生成的区域建议对feature maps做提取,并对提取后的特征做RoI pooling。在RoI pooling之后的东西就和Fast R-CNN一样了。

所以RPN的输入是卷积后的特征图,输出是多个打过分的建议框,所谓打分是对框中是否是物体打分,建议框是四个值(x,y,w,h)。

RPN是一种全卷积网络,它的前五层和Faster R-CNN的前五层是一样的,所以RPN是在进一步的共享卷积层的计算,以降低区域建议的时间消耗。
也是因为共享卷积的原因,所以我们一般认为RPN只有两层。为了更好的理解RPN结构,我们顺便说下它的前五层,这是网络是ZFNet:
这里写图片描述

上图中红色框框出来的就是conv5,也就是这一层的特征别送入到RPN中,特征图的尺寸为13*13*256,RPN在这个特征图上用3*3*256的卷积核,一共用了256个。那么卷积核一次卷积之后的特征就是1*1*256,也就是下图中的256-d,之后该特征出两个分支:
第一个分支(reg layer)用4k个1*1*256的卷积核卷积,最后输出4k个数,这里的4是一个建议框的参数,即(x,y,w,h);
第二个分支(cls layer)用2k个1*1*256的卷积核卷积,最后输出2k个数,这里的2是该区域到底有没有物体,即(object,non-object)。

这里写图片描述
那么,k是什么呢?
k是Anchor box(参考框)的类型数,在Faster R-CNN中k=9,分别是3个尺度scale和3个比例ratio,其中:
scale为(128,256,512)
ratio为 1:1,1:2,2:1
这里写图片描述 这里写图片描述
参考框的中心就是卷积核的中心。
所以,在conv5层上,用3*3卷积核每卷积一次,都会生成k个参考框,那么参考框的总数就应该是W*H*K,如上所说,conv5的尺寸为13*13的话,那么生成的Anchor box的总数就是1521个。

然后我们就会发现通过上面的解释,RPN有一些地方是说不通的,下面我们一一解释下这些坑:
1.上面提到Anchor box的总数是1521个,那为什么说RPN生成300个左右的区域建议呢?
每一个参考框都会有一个是不是物体的打分,在检测过程中RPN计算所有的参考框后会选择其中300个得分最高的区域。

2.参考框中的尺寸为(128,256,512),但是conv5的尺寸只有13*13,在哪里生成这些参考框呢?
这些参考框不是在特征图上生成,而是在原图上,而原图之前的尺寸也不是224*244,这个尺寸是原图经过压缩得到的。所以RPN在做的是将每个点产生的9个参考框来映射原始图像,也就是通过4k个位置偏移输出和k个参考框,得到参考框在原始图像中的位置。就像Fast R-CNN中ss算法,其实也是在原图上生成的,最后只是经过了坐标变化才能在conv5上提取。

1.在卷积核卷积到一个点的时候,输出了9个参考框,但是这9个建议框的特征是相同的,都是256个3*3*256卷积核卷积得到的1*1*256的特征,那么这9个参考框在哪里引导的RPN关注这些区域呢?
特征确实是相同的,但是得到的特征最终是要向原图做映射的,以得到最终的区域建议,而相同的特征对应了9种不同的参考映射方式,于是相同的特征,映射给不同的参考框时,loss是不同的。那么哪种方式是做好的呢,当然是loss最小的那个。所以不同的9个参考框,它们的区别并不体现在特征上,而是在loss上,我们下面就看下RPN的损失函数。

RPN损失函数

首先给出函数的公式:
这里写图片描述
这个公式和Fast R-CNN的多任务损失其实很像,同样是一个在做分类,一个在做回归,然后把两个函数加在一起。i是一个batch中anchor box的索引。

用于分类的loss:

这里写图片描述

这依然是一个负的log值,,Pi为第i个参考框是物体的预测概率,Pi*是一个指示函数,如果anchor是物体的话,Pi* 就是1;如果anchor是背景,那么Pi* 就是0。
那么如果某一个区域是物体的话,如果pi=1,pi*=1,此时的损失函数为0;同理pi=0的话,损失函数为正无穷。

用于回归的loss:
这里写图片描述

其中R还是smooth L1平滑方程:

这里写图片描述

同样的背景没有边界框,所以需要Pi* Lreg。

而ti与ti*分布对应四个值,分别是x,y,w,h的坐标偏差,其中:
x,y,w,h是预测框(就是reg layer的输出);
xa,ya,wa,ha是anchor参考框;
x*,y*,w*,h*是ground truth框;
这里写图片描述

ti是预测框与anchor之间的偏差,ti*是ground truth与anchor之间的偏差,那么我们考虑一种情况,那就是ti与ti*与相同了,此时损失函数就是0,那么这意味着:
预测值与anchor之间的偏差=ground truth与anchor之间的偏差
也就是说预测值完全等于ground truth。这就是上面提到的注意机制引导RPN关注anchor的过程,当anchor不同的时候,loss函数是不同的。所以这是一个反向的过程,我们选择出来了某一个点上3*3范围内的特征,那么这个特征是物体还是背景呢,还有就是它对应原图中哪个区域的时候,效果是最好的呢?这就是RPN要解决的问题。

在这里顺便说一下个人的一个想法,会更方便理解,RPN在conv5上用3*3的卷积核卷积,那么如果原图上某一个区域在conv5上的大小恰好就是3*3呢?那么这个卷积就相当于一个全尺寸卷积了,显然它是可以学习到这个区域内的所有特征的,然后我们再看下这些尺寸,这方便我们理解为什么RPN选择了3*3卷积。
conv5的尺寸为13*13;
卷积为3*3;
原图大小如果是1024;
那么anchor选择为256的时候,它们的比例非常接近:
13/3 = 1024/256
但是原图的尺寸不一定都是1024*1024,所以为了考虑形变与缩放,anchor有9个选择。

Faster R-CNN训练

Faster R-CNN的训练时分步的,但是不是分阶段的,因为end-to-end的问题在fast R-CNN就已经解决了。前面说了Faster R-CNN =Fast R-CNN +RPN,所以训练的过程需要分步来完成,但是每一步都是end-to-end。

Step 1:训练RPN网络;用的是ImageNet上的初始模型,因为RPN是由自己的损失函数的,所以在这里可以先把RPN训练起来,但是在组合mini-batch做梯度回传的时候为了避免负样本(背景)偏多的情况,会人为的我们随机地在一个图像中选择256个anchor,其中采样的正负anchor的比例是1:1。如果一个图像中的正样本数小于128,我们就用负样本填补这个mini-batch。

Step 2:训练Fast R-CNN;训练好RPN之后,单独训练Fast R-CNN,此时Fast R-CNN是不与RPN共享卷积层的,也就是初始模型还是ImageNet上得到的,用的区域建议是RPN生成的,训练的过程在之前的文章就就介绍了。

Step 3:调优RPN,在这一步中将再次训练RPN,这不过这次的前五层卷积核与Fast R-CNN共享,用Step2中的结果初始化RPN,并固定卷积层,finetune剩下的层。

Step 4:调优Fast R-CNN,此时用的区域建议是Step3中调优后的RPN生成的,同样是固定了卷积层,finetune剩下的层。

Faster R-CNN性能评价

这里写图片描述
上面这张图说明了Faster R-CNN的单图测试时间与mAP,可以看到,Fast R-CNN与R-CNN的时间与Object Detection系列(三) Fast R-CNN的说法不一样了,这是因为后者加上了ss算法的时间,大概2s左右的样子。
单图测试时间的大幅缩减,让Fast R-CNN能够真正意义上实现实时检测任务。

作者:chaipp0607 发表于2017/11/13 14:47:15 原文链接
阅读:9 评论:0 查看评论

数据结构与算法分析(Java语言描述)(15)—— 二分查找(递归与非递归)

$
0
0

非递归

int find(int[] arr, int target){

    int l=0, r=arr.length-1;

    while(l<=r){
        int mid = (r-l)/2 + l;
        if(arr[mid] == target) return mid;
        if(arr[mid] > target) r = mid-1;
        if(arr[mid] < target) l = mid+1;
    }
    return -1;
}
package com.algorithm.search;

public class BinarySearchNotRecursive {
    private BinarySearchNotRecursive() {
    }

    // 二分查找法,在有序数组arr中,查找target
    // 如果找到target,返回相应的索引index
    // 如果没有找到target,返回-1
    public static int find(Integer[] arr, Integer target) {

        // 在arr[l...r]之中查找target
        int l = 0, r = arr.length - 1;

        while (l <= r) {

            // 防止极端情况下的整形溢出,使用下面的逻辑求出mid
            int mid = l + (r - l) / 2;

            if (arr[mid].compareTo(target) == 0) {
                return mid;
            }

            if (arr[mid].compareTo(target) > 0) {
                r = mid - 1;
            } else if (arr[mid].compareTo(target) < 0) {
                l = mid + 1;
            }
        }
        return -1;
    }

    // 测试非递归的二分查找算法
    public static void main(String[] args) {

        int N = 1000;
        Integer[] arr = new Integer[N];
        for (int i = 0; i < N; i++)
            arr[i] = new Integer(i);

        // 对于我们的待查找数组[0...N)
        // 对[0...N)区间的数值使用二分查找,最终结果应该就是数字本身
        // 对[N...2*N)区间的数值使用二分查找,因为这些数字不在arr中,结果为-1
        for (int i = 0; i < 2 * N; i++) {
            int v = BinarySearchNotRecursive.find(arr, new Integer(i));
            if (i < N) {
                System.out.print(i);
                System.out.println(v == i);
            } else {
                System.out.print(i);
                System.out.println(v == -1);
            }
        }

        return;
    }

}

递归

int find(int[] arr, int target){
    return find(arr, 0, arr.length-1, target);
}
int find(int[] arr, int l, int r, int target){
    if( l > r) return -1;
    int mid = (r-l)/2 + l;
    if(arr[mid] == target) return mid;
    else if(arr[mid] > target) return find(arr, l, mid1, target);
    else returnfind(arr, mid+1, r, target);
}
package com.algorithm.search;

public class BinarySearchRecursive {
    private BinarySearchRecursive() {
    }

    public static int find(Integer[] arr, Integer target) {
        return find(arr, 0, arr.length - 1, target);
    }

    private static int find(Integer[] arr, int l, int r, Integer target) {

        if (l > r) return -1;

        // 防止极端情况下的整形溢出,使用下面的逻辑求出mid
        int mid = l + (r - l) / 2;

        if (arr[mid].compareTo(target) > 0) {
            return find(arr, l, mid - 1, target);
        } else if (arr[mid].compareTo(target) < 0) {
            return find(arr, mid + 1, r, target);
        } else { // arr[mid].compareTo(target) == 0
            return mid;
        }
    }

    // 测试递归的二分查找算法
    public static void main(String[] args) {

        int N = 1000;
        Integer[] arr = new Integer[N];
        for (int i = 0; i < N; i++)
            arr[i] = new Integer(i);

        // 对于我们的待查找数组[0...N)
        // 对[0...N)区间的数值使用二分查找,最终结果应该就是数字本身
        // 对[N...2*N)区间的数值使用二分查找,因为这些数字不在arr中,结果为-1
        for (int i = 0; i < 2 * N; i++) {
            int v = BinarySearchRecursive.find(arr, new Integer(i));
            if (i < N) {
                System.out.print(i);
                System.out.println(v == i);
            } else {
                System.out.print(i);
                System.out.println(v == -1);
            }
        }

        return;
    }


}

这里写图片描述

作者:HeatDeath 发表于2017/11/13 14:59:49 原文链接
阅读:0 评论:0 查看评论

python数据分析系列教程——NumPy全解

$
0
0

先决条件

在阅读这个教程之前,你多少需要知道点python。如果你想从新回忆下,请看看Python Tutorial.

如果你想要运行教程中的示例,你至少需要在你的电脑上安装了以下一些软件:

这些是可能对你有帮助的:

  • ipython是一个净强化的交互Python Shell,对探索NumPy的特性非常方便。
  • matplotlib将允许你绘图
  • Scipy在NumPy的基础上提供了很多科学模块

基础篇

NumPy的主要对象是同种元素的多维数组。这是一个所有的元素都是一种类型、通过一个正整数元组索引的元素表格(通常是元素是数字)。在NumPy中维度(dimensions)叫做轴(axes),轴的个数叫做秩(rank)。

例如,在3D空间一个点的坐标[1, 2, 3]是一个秩为1的数组,因为它只有一个轴。那个轴长度为3.又例如,在以下例子中,数组的秩为2(它有两个维度).第一个维度长度为2,第二个维度长度为3.

[[ 1., 0., 0.],
 [ 0., 1., 2.]]

NumPy的数组类被称作ndarray。通常被称作数组。注意numpy.array和标准Python库类array.array并不相同,后者只处理一维数组和提供少量功能。更多重要ndarray对象属性有:

  • ndarray.ndim

    数组轴的个数,在python的世界中,轴的个数被称作秩

  • ndarray.shape

    数组的维度。这是一个指示数组在每个维度上大小的整数元组。例如一个n排m列的矩阵,它的shape属性将是(2,3),这个元组的长度显然是秩,即维度或者ndim属性

  • ndarray.size

    数组元素的总个数,等于shape属性中元组元素的乘积。

  • ndarray.dtype

    一个用来描述数组中元素类型的对象,可以通过创造或指定dtype使用标准Python类型。另外NumPy提供它自己的数据类型。

  • ndarray.itemsize

    数组中每个元素的字节大小。例如,一个元素类型为float64的数组itemsiz属性值为8(=64/8),又如,一个元素类型为complex32的数组item属性为4(=32/8).

  • ndarray.data

    包含实际数组元素的缓冲区,通常我们不需要使用这个属性,因为我们总是通过索引来使用数组中的元素。

一个例子1

>>> from numpy  import *
>>> a = arange(15).reshape(3, 5)
>>> a
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
>>> a.shape
(3, 5)
>>> a.ndim
2
>>> a.dtype.name
'int32'
>>> a.itemsize
4
>>> a.size
15
>>> type(a)
numpy.ndarray
>>> b = array([6, 7, 8])
>>> b
array([6, 7, 8])
>>> type(b)
numpy.ndarray

创建数组

有好几种创建数组的方法。

例如,你可以使用array函数从常规的Python列表和元组创造数组。所创建的数组类型由原序列中的元素类型推导而来。

>>> from numpy  import *
>>> a = array( [2,3,4] )
>>> a
array([2, 3, 4])
>>> a.dtype
dtype('int32')
>>> b = array([1.2, 3.5, 5.1])
>>> b.dtype
dtype('float64')  一个常见的错误包括用多个数值参数调用`array`而不是提供一个由数值组成的列表作为一个参数。

>>> a = array(1,2,3,4)    # WRONG

>>> a = array([1,2,3,4])  # RIGHT

数组将序列包含序列转化成二维的数组,序列包含序列包含序列转化成三维数组等等。

>>> b = array( [ (1.5,2,3), (4,5,6) ] )
>>> b
array([[ 1.5,  2. ,  3. ],
       [ 4. ,  5. ,  6. ]])

数组类型可以在创建时显示指定

>>> c = array( [ [1,2], [3,4] ], dtype=complex )
>>> c
array([[ 1.+0.j,  2.+0.j],
       [ 3.+0.j,  4.+0.j]])

通常,数组的元素开始都是未知的,但是它的大小已知。因此,NumPy提供了一些使用占位符创建数组的函数。这最小化了扩展数组的需要和高昂的运算代价。

函数function创建一个全是0的数组,函数ones创建一个全1的数组,函数empty创建一个内容随机并且依赖与内存状态的数组。默认创建的数组类型(dtype)都是float64。

>>> zeros( (3,4) )
array([[0.,  0.,  0.,  0.],
       [0.,  0.,  0.,  0.],
       [0.,  0.,  0.,  0.]])
>>> ones( (2,3,4), dtype=int16 )                # dtype can also be specified
array([[[ 1, 1, 1, 1],
        [ 1, 1, 1, 1],
        [ 1, 1, 1, 1]],
       [[ 1, 1, 1, 1],
        [ 1, 1, 1, 1],
        [ 1, 1, 1, 1]]], dtype=int16)
>>> empty( (2,3) )
array([[  3.73603959e-262,   6.02658058e-154,   6.55490914e-260],
       [  5.30498948e-313,   3.14673309e-307,   1.00000000e+000]])

为了创建一个数列,NumPy提供一个类似arange的函数返回数组而不是列表:

>>> arange( 10, 30, 5 )
array([10, 15, 20, 25])
>>> arange( 0, 2, 0.3 )                 # it accepts float arguments
array([ 0. ,  0.3,  0.6,  0.9,  1.2,  1.5,  1.8])

arange使用浮点数参数时,由于有限的浮点数精度,通常无法预测获得的元素个数。因此,最好使用函数linspace去接收我们想要的元素个数来代替用range来指定步长。

其它函数array, zeros, zeros_like, ones, ones_like, empty, empty_like, arange, linspace, rand, randn, fromfunction, fromfile参考:NumPy示例

打印数组

当你打印一个数组,NumPy以类似嵌套列表的形式显示它,但是呈以下布局:

  • 最后的轴从左到右打印
  • 次后的轴从顶向下打印
  • 剩下的轴从顶向下打印,每个切片通过一个空行与下一个隔开

一维数组被打印成行,二维数组成矩阵,三维数组成矩阵列表。

>>> a = arange(6)                         # 1d array
>>> print a
[0 1 2 3 4 5]
>>>
>>> b = arange(12).reshape(4,3)           # 2d array
>>> print b
[[ 0  1  2]
 [ 3  4  5]
 [ 6  7  8]
 [ 9 10 11]]
>>>
>>> c = arange(24).reshape(2,3,4)         # 3d array
>>> print c
[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]

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

查看形状操作一节获得有关reshape的更多细节

如果一个数组用来打印太大了,NumPy自动省略中间部分而只打印角落

>>> print arange(10000)
[   0    1    2 ..., 9997 9998 9999]
>>>
>>> print arange(10000).reshape(100,100)
[[   0    1    2 ...,   97   98   99]
 [ 100  101  102 ...,  197  198  199]
 [ 200  201  202 ...,  297  298  299]
 ...,
 [9700 9701 9702 ..., 9797 9798 9799]
 [9800 9801 9802 ..., 9897 9898 9899]
 [9900 9901 9902 ..., 9997 9998 9999]]

禁用NumPy的这种行为并强制打印整个数组,你可以设置printoptions参数来更改打印选项。

>>> set_printoptions(threshold='nan')

基本运算

数组的算术运算是按元素的。新的数组被创建并且被结果填充。

>>> a = array( [20,30,40,50] )
>>> b = arange( 4 )
>>> b
array([0, 1, 2, 3])
>>> c = a-b
>>> c
array([20, 29, 38, 47])
>>> b**2
array([0, 1, 4, 9])
>>> 10*sin(a)
array([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])
>>> a<35
array([True, True, False, False], dtype=bool)

不像许多矩阵语言,NumPy中的乘法运算符*指示按元素计算,矩阵乘法可以使用dot函数或创建矩阵对象实现(参见教程中的矩阵章节)

>>> A = array( [[1,1],
...             [0,1]] )
>>> B = array( [[2,0],
...             [3,4]] )
>>> A*B                         # elementwise product
array([[2, 0],
       [0, 4]])
>>> dot(A,B)                    # matrix product
array([[5, 4],
       [3, 4]])

有些操作符像+=*=被用来更改已存在数组而不创建一个新的数组。

>>> a = ones((2,3), dtype=int)
>>> b = random.random((2,3))
>>> a *= 3
>>> a
array([[3, 3, 3],
       [3, 3, 3]])
>>> b += a
>>> b
array([[ 3.69092703,  3.8324276 ,  3.0114541 ],
       [ 3.18679111,  3.3039349 ,  3.37600289]])
>>> a += b                                  # b is converted to integer type
>>> a
array([[6, 6, 6],
       [6, 6, 6]])

当运算的是不同类型的数组时,结果数组和更普遍和精确的已知(这种行为叫做upcast)。

>>> a = ones(3, dtype=int32)
>>> b = linspace(0,pi,3)
>>> b.dtype.name
'float64'
>>> c = a+b
>>> c
array([ 1.        ,  2.57079633,  4.14159265])
>>> c.dtype.name
'float64'
>>> d = exp(c*1j)
>>> d
array([ 0.54030231+0.84147098j, -0.84147098+0.54030231j,
       -0.54030231-0.84147098j])
>>> d.dtype.name
'complex128' 许多非数组运算,如计算数组所有元素之和,被作为ndarray类的方法实现

>>> a = random.random((2,3))
>>> a
array([[ 0.6903007 ,  0.39168346,  0.16524769],
       [ 0.48819875,  0.77188505,  0.94792155]])
>>> a.sum()
3.4552372100521485
>>> a.min()
0.16524768654743593
>>> a.max()
0.9479215542670073

这些运算默认应用到数组好像它就是一个数字组成的列表,无关数组的形状。然而,指定axis参数你可以吧运算应用到数组指定的轴上:

>>> b = arange(12).reshape(3,4)
>>> b
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> b.sum(axis=0)                            # sum of each column
array([12, 15, 18, 21])
>>>
>>> b.min(axis=1)                            # min of each row
array([0, 4, 8])
>>>
>>> b.cumsum(axis=1)                         # cumulative sum along each row
array([[ 0,  1,  3,  6],
       [ 4,  9, 15, 22],
       [ 8, 17, 27, 38]])

通用函数(ufunc)

NumPy提供常见的数学函数如sin,cosexp。在NumPy中,这些叫作“通用函数”(ufunc)。在NumPy里这些函数作用按数组的元素运算,产生一个数组作为输出。

>>> B = arange(3)
>>> B
array([0, 1, 2])
>>> exp(B)
array([ 1.        ,  2.71828183,  7.3890561 ])
>>> sqrt(B)
array([ 0.        ,  1.        ,  1.41421356])
>>> C = array([2., -1., 4.])
>>> add(B, C)
array([ 2.,  0.,  6.])

更多函数all, alltrue, any, apply along axis, argmax, argmin, argsort, average, bincount, ceil, clip, conj, conjugate, corrcoef, cov, cross, cumprod, cumsum, diff, dot, floor, inner, inv, lexsort, max, maximum, mean, median, min, minimum, nonzero, outer, prod, re, round, sometrue, sort, std, sum, trace, transpose, var, vdot, vectorize, where 参见:NumPy示例

索引,切片和迭代

一维数组可以被索引、切片和迭代,就像列表和其它Python序列。

>>> a = arange(10)**3
>>> a
array([  0,   1,   8,  27,  64, 125, 216, 343, 512, 729])
>>> a[2]
8
>>> a[2:5]
array([ 8, 27, 64])
>>> a[:6:2] = -1000    # equivalent to a[0:6:2] = -1000; from start to position 6, exclusive, set every 2nd element to -1000
>>> a
array([-1000,     1, -1000,    27, -1000,   125,   216,   343,   512,   729])
>>> a[ : :-1]                                 # reversed a
array([  729,   512,   343,   216,   125, -1000,    27, -1000,     1, -1000])
>>> for i in a:
...         print i**(1/3.),
...
nan 1.0 nan 3.0 nan 5.0 6.0 7.0 8.0 9.0

多维数组可以每个轴有一个索引。这些索引由一个逗号分割的元组给出。

>>> def f(x,y):
...         return 10*x+y
...
>>> b = fromfunction(f,(5,4),dtype=int)
>>> b
array([[ 0,  1,  2,  3],
       [10, 11, 12, 13],
       [20, 21, 22, 23],
       [30, 31, 32, 33],
       [40, 41, 42, 43]])
>>> b[2,3]
23
>>> b[0:5, 1]                       # each row in the second column of b
array([ 1, 11, 21, 31, 41])
>>> b[ : ,1]                        # equivalent to the previous example
array([ 1, 11, 21, 31, 41])
>>> b[1:3, : ]                      # each column in the second and third row of b
array([[10, 11, 12, 13],
       [20, 21, 22, 23]])

当少于轴数的索引被提供时,确失的索引被认为是整个切片:

>>> b[-1]                                  # the last row. Equivalent to b[-1,:]
array([40, 41, 42, 43])

b[i]中括号中的表达式被当作i和一系列:,来代表剩下的轴。NumPy也允许你使用“点”像b[i,...]

(…)代表许多产生一个完整的索引元组必要的分号。如果x是秩为5的数组(即它有5个轴),那么:

  • x[1,2,…] 等同于 x[1,2,:,:,:],
  • x[…,3] 等同于 x[:,:,:,:,3]
  • x[4,…,5,:] 等同 x[4,:,:,5,:].
>>> c = array( [ [[  0,  1,  2],      # a 3D array (two stacked 2D arrays) ...               [ 10, 12, 13]], ... ...              [[100,101,102], ...               [110,112,113]] ] ) >>> c.shape (2, 2, 3) >>> c[1,...]                          # same as c[1,:,:] or c[1] array([[100, 101, 102],        [110, 112, 113]]) >>> c[...,2]                          # same as c[:,:,2] array([[  2,  13],        [102, 113]]) 

迭代多维数组是就第一个轴而言的:2

>>> for row in b:
...         print row
...
[0 1 2 3]
[10 11 12 13]
[20 21 22 23]
[30 31 32 33]
[40 41 42 43]

然而,如果一个人想对每个数组中元素进行运算,我们可以使用flat属性,该属性是数组元素的一个迭代器:

>>> for element in b.flat:
...         print element,
...
0 1 2 3 10 11 12 13 20 21 22 23 30 31 32 33 40 41 42 43

更多[], …, newaxis, ndenumerate, indices, index exp 参考NumPy示例

形状操作

更改数组的形状

一个数组的形状由它每个轴上的元素个数给出:

>>> a = floor(10*random.random((3,4)))
>>> a
array([[ 7.,  5.,  9.,  3.],
       [ 7.,  2.,  7.,  8.],
       [ 6.,  8.,  3.,  2.]])
>>> a.shape
(3, 4)

一个数组的形状可以被多种命令修改:

>>> a.ravel() # flatten the array
array([ 7.,  5.,  9.,  3.,  7.,  2.,  7.,  8.,  6.,  8.,  3.,  2.])
>>> a.shape = (6, 2)
>>> a.transpose()
array([[ 7.,  9.,  7.,  7.,  6.,  3.],
       [ 5.,  3.,  2.,  8.,  8.,  2.]])

ravel()展平的数组元素的顺序通常是“C风格”的,就是说,最右边的索引变化得最快,所以元素a[0,0]之后是a[0,1]。如果数组被改变形状(reshape)成其它形状,数组仍然是“C风格”的。NumPy通常创建一个以这个顺序保存数据的数组,所以ravel()将总是不需要复制它的参数3。但是如果数组是通过切片其它数组或有不同寻常的选项时,它可能需要被复制。函数reshape()ravel()还可以被同过一些可选参数构建成FORTRAN风格的数组,即最左边的索引变化最快。

reshape函数改变参数形状并返回它,而resize函数改变数组自身。

>>> a
array([[ 7.,  5.],
       [ 9.,  3.],
       [ 7.,  2.],
       [ 7.,  8.],
       [ 6.,  8.],
       [ 3.,  2.]])
>>> a.resize((2,6))
>>> a
array([[ 7.,  5.,  9.,  3.,  7.,  2.],
       [ 7.,  8.,  6.,  8.,  3.,  2.]])

如果在改变形状操作中一个维度被给做-1,其维度将自动被计算

更多 shape, reshape, resize, ravel 参考NumPy示例

组合(stack)不同的数组

几种方法可以沿不同轴将数组堆叠在一起:

>>> a = floor(10*random.random((2,2)))
>>> a
array([[ 1.,  1.],
       [ 5.,  8.]])
>>> b = floor(10*random.random((2,2)))
>>> b
array([[ 3.,  3.],
       [ 6.,  0.]])
>>> vstack((a,b))
array([[ 1.,  1.],
       [ 5.,  8.],
       [ 3.,  3.],
       [ 6.,  0.]])
>>> hstack((a,b))
array([[ 1.,  1.,  3.,  3.],
       [ 5.,  8.,  6.,  0.]])

函数column_stack以列将一维数组合成二维数组,它等同与vstack对一维数组。

>>> column_stack((a,b))   # With 2D arrays
array([[ 1.,  1.,  3.,  3.],
       [ 5.,  8.,  6.,  0.]])
>>> a=array([4.,2.])
>>> b=array([2.,8.])
>>> a[:,newaxis]  # This allows to have a 2D columns vector
array([[ 4.],
       [ 2.]])
>>> column_stack((a[:,newaxis],b[:,newaxis]))
array([[ 4.,  2.],
       [ 2.,  8.]])
>>> vstack((a[:,newaxis],b[:,newaxis])) # The behavior of vstack is different
array([[ 4.],
       [ 2.],
       [ 2.],
       [ 8.]])

row_stack函数,另一方面,将一维数组以行组合成二维数组。

对那些维度比二维更高的数组,hstack沿着第二个轴组合,vstack沿着第一个轴组合,concatenate允许可选参数给出组合时沿着的轴。

Note

在复杂情况下,r_[]c_[]对创建沿着一个方向组合的数很有用,它们允许范围符号(“:”):

>>> r_[1:4,0,4]
array([1, 2, 3, 0, 4])

当使用数组作为参数时,r_c_的默认行为和vstackhstack很像,但是允许可选的参数给出组合所沿着的轴的代号。

更多函数hstack , vstack, column_stack , row_stack , concatenate , c_ , r_ 参见NumPy示例.

将一个数组分割(split)成几个小数组

使用hsplit你能将数组沿着它的水平轴分割,或者指定返回相同形状数组的个数,或者指定在哪些列后发生分割:

>>> a = floor(10*random.random((2,12)))
>>> a
array([[ 8.,  8.,  3.,  9.,  0.,  4.,  3.,  0.,  0.,  6.,  4.,  4.],
       [ 0.,  3.,  2.,  9.,  6.,  0.,  4.,  5.,  7.,  5.,  1.,  4.]])
>>> hsplit(a,3)   # Split a into 3
[array([[ 8.,  8.,  3.,  9.],
       [ 0.,  3.,  2.,  9.]]), array([[ 0.,  4.,  3.,  0.],
       [ 6.,  0.,  4.,  5.]]), array([[ 0.,  6.,  4.,  4.],
       [ 7.,  5.,  1.,  4.]])]
>>> hsplit(a,(3,4))   # Split a after the third and the fourth column
[array([[ 8.,  8.,  3.],
       [ 0.,  3.,  2.]]), array([[ 9.],
       [ 9.]]), array([[ 0.,  4.,  3.,  0.,  0.,  6.,  4.,  4.],
       [ 6.,  0.,  4.,  5.,  7.,  5.,  1.,  4.]])]

vsplit沿着纵向的轴分割,array split允许指定沿哪个轴分割。

复制和视图

当运算和处理数组时,它们的数据有时被拷贝到新的数组有时不是。这通常是新手的困惑之源。这有三种情况:

完全不拷贝

简单的赋值不拷贝数组对象或它们的数据。

>>> a = arange(12)
>>> b = a            # no new object is created
>>> b is a           # a and b are two names for the same ndarray object
True
>>> b.shape = 3,4    # changes the shape of a
>>> a.shape
(3, 4)

Python 传递不定对象作为参考4,所以函数调用不拷贝数组。

>>> def f(x):
...     print id(x)
...
>>> id(a)                           # id is a unique identifier of an object
148293216
>>> f(a)
148293216

视图(view)和浅复制

不同的数组对象分享同一个数据。视图方法创造一个新的数组对象指向同一数据。

>>> c = a.view()
>>> c is a
False
>>> c.base is a                        # c is a view of the data owned by a
True
>>> c.flags.owndata
False
>>>
>>> c.shape = 2,6                      # a's shape doesn't change
>>> a.shape
(3, 4)
>>> c[0,4] = 1234                      # a's data changes
>>> a
array([[   0,    1,    2,    3],
       [1234,    5,    6,    7],
       [   8,    9,   10,   11]])

切片数组返回它的一个视图:

>>> s = a[ : , 1:3]     # spaces added for clarity; could also be written "s = a[:,1:3]"
>>> s[:] = 10           # s[:] is a view of s. Note the difference between s=10 and s[:]=10
>>> a
array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

深复制

这个复制方法完全复制数组和它的数据。

>>> d = a.copy()                          # a new array object with new data is created
>>> d is a
False
>>> d.base is a                           # d doesn't share anything with a
False
>>> d[0,0] = 9999
>>> a
array([[   0,   10,   10,    3],
       [1234,   10,   10,    7],
       [   8,   10,   10,   11]])

函数和方法(method)总览

这是个NumPy函数和方法分类排列目录。这些名字链接到NumPy示例,你可以看到这些函数起作用。[^5]

创建数组

arange, array, copy, empty, empty_like, eye, fromfile, fromfunction, identity, linspace, logspace, mgrid, ogrid, ones, ones_like, r , zeros, zeros_like 

转化

astype, atleast 1d, atleast 2d, atleast 3d, mat 

操作

array split, column stack, concatenate, diagonal, dsplit, dstack, hsplit, hstack, item, newaxis, ravel, repeat, reshape, resize, squeeze, swapaxes, take, transpose, vsplit, vstack 

询问

all, any, nonzero, where 

排序

argmax, argmin, argsort, max, min, ptp, searchsorted, sort 

运算

choose, compress, cumprod, cumsum, inner, fill, imag, prod, put, putmask, real, sum 

基本统计

cov, mean, std, var 

基本线性代数

cross, dot, outer, svd, vdot

进阶

广播法则(rule)

广播法则能使通用函数有意义地处理不具有相同形状的输入。

广播第一法则是,如果所有的输入数组维度不都相同,一个“1”将被重复地添加在维度较小的数组上直至所有的数组拥有一样的维度。

广播第二法则确定长度为1的数组沿着特殊的方向表现地好像它有沿着那个方向最大形状的大小。对数组来说,沿着那个维度的数组元素的值理应相同。

应用广播法则之后,所有数组的大小必须匹配。更多细节可以从这个文档找到。

花哨的索引和索引技巧

NumPy比普通Python序列提供更多的索引功能。除了索引整数和切片,正如我们之前看到的,数组可以被整数数组和布尔数组索引。

通过数组索引

>>> a = arange(12)**2                          # the first 12 square numbers
>>> i = array( [ 1,1,3,8,5 ] )                 # an array of indices
>>> a[i]                                       # the elements of a at the positions i
array([ 1,  1,  9, 64, 25])
>>>
>>> j = array( [ [ 3, 4], [ 9, 7 ] ] )         # a bidimensional array of indices
>>> a[j]                                       # the same shape as j
array([[ 9, 16],
       [81, 49]])

当被索引数组a是多维的时,每一个唯一的索引数列指向a的第一维5。以下示例通过将图片标签用调色版转换成色彩图像展示了这种行为。

>>> palette = array( [ [0,0,0],                # black
...                    [255,0,0],              # red
...                    [0,255,0],              # green
...                    [0,0,255],              # blue
...                    [255,255,255] ] )       # white
>>> image = array( [ [ 0, 1, 2, 0 ],           # each value corresponds to a color in the palette
...                  [ 0, 3, 4, 0 ]  ] )
>>> palette[image]                            # the (2,4,3) color image
array([[[  0,   0,   0],
        [255,   0,   0],
        [  0, 255,   0],
        [  0,   0,   0]],
       [[  0,   0,   0],
        [  0,   0, 255],
        [255, 255, 255],
        [  0,   0,   0]]])

我们也可以给出不不止一维的索引,每一维的索引数组必须有相同的形状。

>>> a = arange(12).reshape(3,4)
>>> a
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>> i = array( [ [0,1],                        # indices for the first dim of a
...              [1,2] ] )
>>> j = array( [ [2,1],                        # indices for the second dim
...              [3,3] ] )
>>>
>>> a[i,j]                                     # i and j must have equal shape
array([[ 2,  5],
       [ 7, 11]])
>>>
>>> a[i,2]
array([[ 2,  6],
       [ 6, 10]])
>>>
>>> a[:,j]                                     # i.e., a[ : , j]
array([[[ 2,  1],
        [ 3,  3]],
       [[ 6,  5],
        [ 7,  7]],
       [[10,  9],
        [11, 11]]])

自然,我们可以把i和j放到序列中(比如说列表)然后通过list索引。

>>> l = [i,j]
>>> a[l]                                       # equivalent to a[i,j]
array([[ 2,  5],
       [ 7, 11]])

然而,我们不能把i和j放在一个数组中,因为这个数组将被解释成索引a的第一维。

>>> s = array( [i,j] )
>>> a[s]                                       # not what we want
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-100-b912f631cc75> in <module>()
----> 1 a[s]

IndexError: index (3) out of range (0<=index<2) in dimension 0
>>>
>>> a[tuple(s)]                                # same as a[i,j]
array([[ 2,  5],
       [ 7, 11]])

另一个常用的数组索引用法是搜索时间序列最大值6

>>> time = linspace(20, 145, 5)                 # time scale
>>> data = sin(arange(20)).reshape(5,4)         # 4 time-dependent series
>>> time
array([  20.  ,   51.25,   82.5 ,  113.75,  145.  ])
>>> data
array([[ 0.        ,  0.84147098,  0.90929743,  0.14112001],
       [-0.7568025 , -0.95892427, -0.2794155 ,  0.6569866 ],
       [ 0.98935825,  0.41211849, -0.54402111, -0.99999021],
       [-0.53657292,  0.42016704,  0.99060736,  0.65028784],
       [-0.28790332, -0.96139749, -0.75098725,  0.14987721]])
>>>
>>> ind = data.argmax(axis=0)                   # index of the maxima for each series
>>> ind
array([2, 0, 3, 1])
>>>
>>> time_max = time[ ind]                       # times corresponding to the maxima
>>>
>>> data_max = data[ind, xrange(data.shape[1])] # => data[ind[0],0], data[ind[1],1]...
>>>
>>> time_max
array([  82.5 ,   20.  ,  113.75,   51.25])
>>> data_max
array([ 0.98935825,  0.84147098,  0.99060736,  0.6569866 ])
>>>
>>> all(data_max == data.max(axis=0))
True

你也可以使用数组索引作为目标来赋值:

>>> a = arange(5)
>>> a
array([0, 1, 2, 3, 4])
>>> a[[1,3,4]] = 0
>>> a
array([0, 0, 2, 0, 0])

然而,当一个索引列表包含重复时,赋值被多次完成,保留最后的值:

>>> a = arange(5)
>>> a[[0,0,2]]=[1,2,3]
>>> a
array([2, 1, 3, 3, 4])

这足够合理,但是小心如果你想用Python的+=结构,可能结果并非你所期望:

>>> a = arange(5)
>>> a[[0,0,2]]+=1
>>> a
array([1, 1, 3, 3, 4])

即使0在索引列表中出现两次,索引为0的元素仅仅增加一次。这是因为Python要求a+=1a=a+1等同。

通过布尔数组索引

当我们使用整数数组索引数组时,我们提供一个索引列表去选择。通过布尔数组索引的方法是不同的我们显式地选择数组中我们想要和不想要的元素。

我们能想到的使用布尔数组的索引最自然方式就是使用和原数组一样形状的布尔数组。

>>> a = arange(12).reshape(3,4)
>>> b = a > 4
>>> b                                          # b is a boolean with a's shape
array([[False, False, False, False],
       [False, True, True, True],
       [True, True, True, True]], dtype=bool)
>>> a[b]                                       # 1d array with the selected elements
array([ 5,  6,  7,  8,  9, 10, 11])

这个属性在赋值时非常有用:

>>> a[b] = 0                                   # All elements of 'a' higher than 4 become 0
>>> a
array([[0, 1, 2, 3],
       [4, 0, 0, 0],
       [0, 0, 0, 0]])

你可以参考曼德博集合示例看看如何使用布尔索引来生成曼德博集合的图像。

第二种通过布尔来索引的方法更近似于整数索引;对数组的每个维度我们给一个一维布尔数组来选择我们想要的切片。

>>> a = arange(12).reshape(3,4)
>>> b1 = array([False,True,True])             # first dim selection
>>> b2 = array([True,False,True,False])       # second dim selection
>>>
>>> a[b1,:]                                   # selecting rows
array([[ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> a[b1]                                     # same thing
array([[ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> a[:,b2]                                   # selecting columns
array([[ 0,  2],
       [ 4,  6],
       [ 8, 10]])
>>>
>>> a[b1,b2]                                  # a weird thing to do
array([ 4, 10])

注意一维数组的长度必须和你想要切片的维度或轴的长度一致,在之前的例子中,b1是一个秩为1长度为三的数组(a的行数),b2(长度为4)与a的第二秩(列)相一致。7

ix_()函数

ix_函数可以为了获得多元组的结果而用来结合不同向量。例如,如果你想要用所有向量a、b和c元素组成的三元组来计算a+b*c

>>> a = array([2,3,4,5])
>>> b = array([8,5,4])
>>> c = array([5,4,6,8,3])
>>> ax,bx,cx = ix_(a,b,c)
>>> ax
array([[[2]],

       [[3]],

       [[4]],

       [[5]]])
>>> bx
array([[[8],
        [5],
        [4]]])
>>> cx
array([[[5, 4, 6, 8, 3]]])
>>> ax.shape, bx.shape, cx.shape
((4, 1, 1), (1, 3, 1), (1, 1, 5))
>>> result = ax+bx*cx
>>> result
array([[[42, 34, 50, 66, 26],
        [27, 22, 32, 42, 17],
        [22, 18, 26, 34, 14]],
       [[43, 35, 51, 67, 27],
        [28, 23, 33, 43, 18],
        [23, 19, 27, 35, 15]],
       [[44, 36, 52, 68, 28],
        [29, 24, 34, 44, 19],
        [24, 20, 28, 36, 16]],
       [[45, 37, 53, 69, 29],
        [30, 25, 35, 45, 20],
        [25, 21, 29, 37, 17]]])
>>> result[3,2,4]
17
>>> a[3]+b[2]*c[4]
17

你也可以实行如下简化:

def ufunc_reduce(ufct, *vectors):
    vs = ix_(*vectors)
    r = ufct.identity
    for v in vs:
        r = ufct(r,v)
    return r

然后这样使用它:

>>> ufunc_reduce(add,a,b,c)
array([[[15, 14, 16, 18, 13],
        [12, 11, 13, 15, 10],
        [11, 10, 12, 14,  9]],
       [[16, 15, 17, 19, 14],
        [13, 12, 14, 16, 11],
        [12, 11, 13, 15, 10]],
       [[17, 16, 18, 20, 15],
        [14, 13, 15, 17, 12],
        [13, 12, 14, 16, 11]],
       [[18, 17, 19, 21, 16],
        [15, 14, 16, 18, 13],
        [14, 13, 15, 17, 12]]])

这个reduce与ufunc.reduce(比如说add.reduce)相比的优势在于它利用了广播法则,避免了创建一个输出大小乘以向量个数的参数数组。8

用字符串索引

参见RecordArray

线性代数

继续前进,基本线性代数包含在这里。

简单数组运算

参考numpy文件夹中的linalg.py获得更多信息

>>> from numpy import *
>>> from numpy.linalg import *

>>> a = array([[1.0, 2.0], [3.0, 4.0]])
>>> print a
[[ 1.  2.]
 [ 3.  4.]]

>>> a.transpose()
array([[ 1.,  3.],
       [ 2.,  4.]])

>>> inv(a)
array([[-2. ,  1. ],
       [ 1.5, -0.5]])

>>> u = eye(2) # unit 2x2 matrix; "eye" represents "I"
>>> u
array([[ 1.,  0.],
       [ 0.,  1.]])
>>> j = array([[0.0, -1.0], [1.0, 0.0]])

>>> dot (j, j) # matrix product
array([[-1.,  0.],
       [ 0., -1.]])

>>> trace(u)  # trace
 2.0

>>> y = array([[5.], [7.]])
>>> solve(a, y)
array([[-3.],
       [ 4.]])

>>> eig(j)
(array([ 0.+1.j,  0.-1.j]),
array([[ 0.70710678+0.j,  0.70710678+0.j],
       [ 0.00000000-0.70710678j,  0.00000000+0.70710678j]]))
Parameters:
    square matrix

Returns
    The eigenvalues, each repeated according to its multiplicity.

    The normalized (unit "length") eigenvectors, such that the
    column ``v[:,i]`` is the eigenvector corresponding to the
    eigenvalue ``w[i]`` .

矩阵类

这是一个关于矩阵类的简短介绍。

>>> A = matrix('1.0 2.0; 3.0 4.0')
>>> A
[[ 1.  2.]
 [ 3.  4.]]
>>> type(A)  # file where class is defined
<class 'numpy.matrixlib.defmatrix.matrix'>

>>> A.T  # transpose
[[ 1.  3.]
 [ 2.  4.]]

>>> X = matrix('5.0 7.0')
>>> Y = X.T
>>> Y
[[5.]
 [7.]]

>>> print A*Y  # matrix multiplication
[[19.]
 [43.]]

>>> print A.I  # inverse
[[-2.   1. ]
 [ 1.5 -0.5]]

>>> solve(A, Y)  # solving linear equation
matrix([[-3.],
        [ 4.]])

索引:比较矩阵和二维数组

注意NumPy中数组和矩阵有些重要的区别。NumPy提供了两个基本的对象:一个N维数组对象和一个通用函数对象。其它对象都是建构在它们之上 的。特别的,矩阵是继承自NumPy数组对象的二维数组对象。对数组和矩阵,索引都必须包含合适的一个或多个这些组合:整数标量、省略号 (ellipses)、整数列表;布尔值,整数或布尔值构成的元组,和一个一维整数或布尔值数组。矩阵可以被用作矩阵的索引,但是通常需要数组、列表或者 其它形式来完成这个任务。

像平常在Python中一样,索引是从0开始的。传统上我们用矩形的行和列表示一个二维数组或矩阵,其中沿着0轴的方向被穿过的称作行,沿着1轴的方向被穿过的是列。9

让我们创建数组和矩阵用来切片:

>>> A = arange(12)
>>> A
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
>>> A.shape = (3,4)
>>> M = mat(A.copy())
>>> print type(A),"  ",type(M)
<type 'numpy.ndarray'>    <class 'numpy.core.defmatrix.matrix'>
>>> print A
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
>>> print M
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

现在,让我们简单的切几片。基本的切片使用切片对象或整数。例如,A[:]M[:]的求值将表现得和Python索引很相似。然而要注意很重要的一点就是NumPy切片数组创建数据的副本;切片提供统一数据的视图。

>>> print A[:]; print A[:].shape
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
(3, 4)
>>> print M[:]; print M[:].shape
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
(3, 4)

现在有些和Python索引不同的了:你可以同时使用逗号分割索引来沿着多个轴索引。

>>> print A[:,1]; print A[:,1].shape
[1 5 9]
(3,)
>>> print M[:,1]; print M[:,1].shape
[[1]
 [5]
 [9]]
(3, 1)

注意最后两个结果的不同。对二维数组使用一个冒号产生一个一维数组,然而矩阵产生了一个二维矩阵。10例如,一个M[2,:]切片产生了一个形状为(1,4)的矩阵,相比之下,一个数组的切片总是产生一个最低可能维度11的数组。例如,如果C是一个三维数组,C[...,1]产生一个二维的数组而C[1,:,1]产生一个一维数组。从这时开始,如果相应的矩阵切片结果是相同的话,我们将只展示数组切片的结果。

假如我们想要一个数组的第一列和第三列,一种方法是使用列表切片:

>>> A[:,[1,3]]
array([[ 1,  3],
       [ 5,  7],
       [ 9, 11]])

稍微复杂点的方法是使用take()方法(method):

>>> A[:,].take([1,3],axis=1)
array([[ 1,  3],
       [ 5,  7],
       [ 9, 11]])

如果我们想跳过第一行,我们可以这样:

>>> A[1:,].take([1,3],axis=1)
array([[ 5,  7],
       [ 9, 11]])

或者我们仅仅使用A[1:,[1,3]]。还有一种方法是通过矩阵向量积(叉积)。

>>> A[ix_((1,2),(1,3))]
array([[ 5,  7],
       [ 9, 11]])

为了读者的方便,在次写下之前的矩阵:

>>> A[ix_((1,2),(1,3))]
array([[ 5,  7],
       [ 9, 11]])

现在让我们做些更复杂的。比如说我们想要保留第一行大于1的列。一种方法是创建布尔索引:

>>> A[0,:]>1
array([False, False, True, True], dtype=bool)
>>> A[:,A[0,:]>1]
array([[ 2,  3],
       [ 6,  7],
       [10, 11]])

就是我们想要的!但是索引矩阵没这么方便。

>>> M[0,:]>1
matrix([[False, False, True, True]], dtype=bool)
>>> M[:,M[0,:]>1]
matrix([[2, 3]])

这个过程的问题是用“矩阵切片”来切片产生一个矩阵12,但是矩阵有个方便的A属性,它的值是数组呈现的。所以我们仅仅做以下替代:

>>> M[:,M.A[0,:]>1]
matrix([[ 2,  3],
        [ 6,  7],
        [10, 11]])

如果我们想要在矩阵两个方向有条件地切片,我们必须稍微调整策略,代之以:

>>> A[A[:,0]>2,A[0,:]>1]
array([ 6, 11])
>>> M[M.A[:,0]>2,M.A[0,:]>1]
matrix([[ 6, 11]])

我们需要使用向量积ix_:

>>> A[ix_(A[:,0]>2,A[0,:]>1)]
array([[ 6,  7],
       [10, 11]])
>>> M[ix_(M.A[:,0]>2,M.A[0,:]>1)]
matrix([[ 6,  7],
        [10, 11]])

技巧和提示

下面我们给出简短和有用的提示。

“自动”改变形状

更改数组的维度,你可以省略一个尺寸,它将被自动推导出来。

>>> a = arange(30)
>>> a.shape = 2,-1,3  # -1 means "whatever is needed"
>>> a.shape
(2, 5, 3)
>>> a
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]]])

向量组合(stacking)

我们如何用两个相同尺寸的行向量列表构建一个二维数组?在MATLAB中这非常简单:如果x和y是两个相同长度的向量,你仅仅需要做m=[x;y]。在NumPy中这个过程通过函数column_stackdstackhstackvstack来完成,取决于你想要在那个维度上组合。例如:

x = arange(0,10,2)                     # x=([0,2,4,6,8])
y = arange(5)                          # y=([0,1,2,3,4])
m = vstack([x,y])                      # m=([[0,2,4,6,8],
                                       #     [0,1,2,3,4]])
xy = hstack([x,y])                     # xy =([0,2,4,6,8,0,1,2,3,4])

二维以上这些函数背后的逻辑会很奇怪。

参考写个Matlab用户的NumPy指南并且在这里添加你的新发现: )

直方图(histogram)

NumPy中histogram函数应用到一个数组返回一对变量:直方图数组和箱式向量。注意:matplotlib也有一个用来建立直方图的函数(叫作hist,正如matlab中一样)与NumPy中的不同。主要的差别是pylab.hist自动绘制直方图,而numpy.histogram仅仅产生数据。

import numpy
import pylab
# Build a vector of 10000 normal deviates with variance 0.5^2 and mean 2
mu, sigma = 2, 0.5
v = numpy.random.normal(mu,sigma,10000)
# Plot a normalized histogram with 50 bins
pylab.hist(v, bins=50, normed=1)       # matplotlib version (plot)
pylab.show()
# Compute the histogram with numpy and then plot it
(n, bins) = numpy.histogram(v, bins=50, normed=True)  # NumPy version (no plot)
pylab.plot(.5*(bins[1:]+bins[:-1]), n)
pylab.show()


作者:luanpeng825485697 发表于2017/11/13 15:26:21 原文链接
阅读:23 评论:0 查看评论

设计模式之----迭代器模式

$
0
0

一、 定义

提供一种方法顺序访问一容器对象中的各个元素,而又不需要暴露该对象内部的表示。

二、 角色

  • 抽象迭代器(Iterator):定义遍历元素所需方法。
  • 具体迭代器(ConcreteIterator):实现具体的遍历方法
  • 抽象容器(Aggregate):提供iterator()方法的抽象父类或者接口。
  • 具体容器类(ConcreteAggregate):具体容器实现类,实现iterator()方法

相关类图:

这里写图片描述

三、 需求

创建一种新的容器,实现迭代器,获取所有元素,并打印出来。

四、 代码实现

为了代码简单,在具体容器类中维护了一个list,代替复杂操作,只需要看迭代器的原理就好~

4.1 抽象迭代器

/**
 * 抽象迭代器
 *
 * Created by rytong on 2017/11/13.
 */

public interface Iterator<T> {
    /**
     * 是否有下一个元素
     *
     * @return
     */
    public boolean hasNext();

    public T next();
}

4.2 具体迭代器实现

/**
 * 具体的迭代器,迭代器持有容器数据的引用
 *
 * Created by rytong on 2017/11/13.
 */

public class ConcreteIterator<T> implements Iterator<T> {
    /**
     * 从容器类传递过来的所有数据
     */
    private List<T> list = new ArrayList<T>();

    private int cursor = 0;

    public ConcreteIterator(List<T> list) {
        this.list = list;
    }

    @Override
    public boolean hasNext() {
        return cursor != list.size();
    }

    @Override
    public T next() {
        return list.get(cursor++);
    }
}

4.3 抽象容器接口

/**
 * 抽象容器类,提供获取迭代器的方法
 *
 * Created by rytong on 2017/11/13.
 */

public interface Aggregate {

    public Iterator iterator();
}

4.4 具体容器的实现

/**
 * 具体容器类
 * Created by rytong on 2017/11/13.
 */

public class ConcreteAggregate<T> implements Aggregate {

    public List<T> list = new ArrayList<T>();

    public void add(T s){
        list.add(s);
    }
    @Override
    public Iterator iterator() {
        return new ConcreteIterator(list);
    }
}

4.5 客户端调用

    ConcreteAggregate<String> concreteAggregate = new ConcreteAggregate<String>();
    concreteAggregate.add("1");
    concreteAggregate.add("3");
    concreteAggregate.add("7");
    concreteAggregate.add("10");

    Iterator<String> iterator = concreteAggregate.iterator();
    while(iterator.hasNext()){
        String next = iterator.next();
        Log.e(TAG,next);
    }

运行结果:

11-13 16:05:40.187 9707-9707/com.iteratorpattern E/MainActivity: 1
11-13 16:05:40.187 9707-9707/com.iteratorpattern E/MainActivity: 3
11-13 16:05:40.187 9707-9707/com.iteratorpattern E/MainActivity: 7
11-13 16:05:40.187 9707-9707/com.iteratorpattern E/MainActivity: 10

五、 总结

5.1 优点

  1. 简化了遍历方式,对于集合的遍历还是比较麻烦的,如果是数组和有序列表,我们还可以使用游标来取得。但用户需要在对集合很了解的前提下,自行遍历对象,但是对于hash表来说,用户遍历起来就比较麻烦了,引入迭代器后,用户操作就方便多了。
  2. 可以提供多种遍历方式,比如说对有序列表,可以提供正序遍历和倒序遍历,用户只需要获取两种迭代器获取相应的数据即可。

5.2 缺点

  • 对于比较简单的遍历,使用迭代器比较复杂,比起迭代器,开发人员更愿意使用for循环和get方法根据角标来取值。

5.3 适用场景

迭代器模式和集合是共生死的,一般来说,实现了一个集合就得提供这个集合的迭代器,就像java中的Collection,List,Set,Map等,这些集合都有自己的迭代器。假如我们也要实现一个这样的容器,那么也需要引入迭代器。

作者:xwh_1230 发表于2017/11/13 16:36:12 原文链接
阅读:0 评论:0 查看评论

LWC 58:724. Find Pivot Index

$
0
0

LWC 58:724. Find Pivot Index

传送门:724. Find Pivot Index

Problem:

Given an array of integers nums, write a method that returns the “pivot” index of this array.

We define the pivot index as the index where the sum of the numbers to the left of the index is equal to the sum of the numbers to the right of the index.

If no such index exists, we should return -1. If there are multiple pivot indexes, you should return the left-most pivot index.

Example 1:

Input:
nums = [1, 7, 3, 6, 5, 6]
Output: 3
Explanation:
The sum of the numbers to the left of index 3 (nums[3] = 6) is equal to the sum of numbers to the right of index 3.
Also, 3 is the first index where this occurs.

Example 2:

Input:
nums = [1, 2, 3]
Output: -1
Explanation:
There is no index that satisfies the conditions in the problem statement.

Note:

  • The length of nums will be in the range [0, 10000].
  • Each element nums[i] will be an integer in the range [-1000, 1000].

思路:
判断当前坐标点的左侧元素之和是否和右侧元素之和相等,返回第一个符合情况的下标。

空间换时间,说白了,以O(1)的速度查询累加和,以O(n)的速度更新累加和数组即可,再以O(n)的速度搜索符合情况的下标。

代码如下:

    public int pivotIndex(int[] nums) {
        int n = nums.length;
        int[] lf = new int[n];
        int[] rt = new int[n];

        int sum = 0;
        for (int i = 0; i < n; ++i) {
             lf[i] = sum;
             sum += nums[i];
        }

        sum = 0;
        for (int j = n - 1; j >= 0; --j) {
            rt[j] = sum;
            sum += nums[j];
        }

        for (int i = 0; i < n; ++i) {
            if (lf[i] == rt[i]) {
                return i;
            }
        }

        return -1;
    }

累加和版本:

    public int pivotIndex(int[] nums) {
        int n = nums.length;
        int[] sums = new int[n + 1];

        for (int i = 0; i < n; ++i){
            sums[i + 1] = sums[i] + nums[i];
        }

        for (int i = 0; i < n; ++i){
            if (sums[i + 1] == sums[n] - sums[i]){
                return i;
            }
        }

        return -1;
    }
作者:u014688145 发表于2017/11/13 17:05:50 原文链接
阅读:159 评论:0 查看评论

SVN服务备份操作步骤

$
0
0

SVN服务备份操作步骤

1、准备源服务器和目标服务器

源服务器:192.168.1.250

目标服务器:192.168.1.251 root/rootroot

 

2、对目标服务器(251)装SVN服务器, 脚本如下:

yum install subversion

 

 

3、创建一个新的仓库 (svnadmin create 路径),脚本如下:

1
svnadmin create ~/svn-storage-bak

  

 

4、进入svn-storage-bak这个仓库的根目录中,脚本如下:

1
cd ~/svn-storage-bak

  

 

5、复制hooks/pre-revprop-change.tmpl为hooks/pre-revprop-change,脚本如下:

1
cp hooks/pre-revprop-change.tmpl hooks/pre-revprop-change

  

 

6、给上面pre-revprop-change脚本加上执行权限,脚本如下:

1
chmod u+x hooks/pre-revprop-change

  

7、编辑pre-revprop-change脚本, 把最后一行 exit 1 改成exit 0 ,然后保存。操作如下:

vim hooks/pre-revprop-change

 

 

 

8、使用svnsync设置新的仓库同步指向目标仓库(svnsync init file:////当前目标svn仓库路径 ,源仓库的url),脚本如下:

1
svnsync init file:////root/svn-storage-bak svn://192.168.1.250/

  


9、然后输入当前服务器的用户名密码。 以及源svn服务器的用户名和密码

 

10、进行svn同步备份 ,脚本如下: 

svnsync sync file:///home/usrname/svn-storage-bak

 

 

然后就开始备份了

 

11、将 第10步的同步脚本,写在一个脚本文件里面,并把日志重定向输出,便于之后查看同步是否成功。 然后设置到crontab 定时任务里面。 定点每天凌晨1点执行。

(我现在是直接把同步脚本写在了crontab里面,建议单独写个脚本文件去定时执行。 )

我的定时任务写法(参考):

 

 

 

12、启动SVN服务,具体脚本如下:

svnserve -d -r /root/svn-storage-bak

 

 

13、测试,查看是否同步成功

用TortoiseSVN客户端或者浏览器访问 ,备份的svn服务器地址。看看资源是否备份成功。

例如:我用219做目标服务器的一个测试。

作者:shijing266 发表于2017/11/13 17:18:04 原文链接
阅读:599 评论:0 查看评论

简单酷炫的网页登录界面

$
0
0


login.html:

<!DOCTYPE html>
<html >
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
    <meta http-equiv="Content-Language" content="zh-CN" />

    <title>Login</title>
    <link rel="stylesheet" type="text/css" href="Login.css"/>
</head>
<body>
    <div id="login">
        <h1>Login</h1>
        <form method="post">
            <input type="text" required="required" placeholder="用户名" name="u"></input>
            <input type="password" required="required" placeholder="密码" name="p"></input>
            <button class="but" type="submit">登录</button>
        </form>
    </div>
</body>
</html>

Login.css:

html{
    width: 100%;
    height: 100%;
    overflow: hidden;
    font-style: sans-serif;
}
body{
    width: 100%;
    height: 100%;
    font-family: 'Open Sans',sans-serif;
    margin: 0;
    background-color: #4A374A;
}
#login{
    position: absolute;
    top: 50%;
    left:50%;
    margin: -150px 0 0 -150px;
    width: 300px;
    height: 300px;
}
#login h1{
    color: #fff;
    text-shadow:0 0 10px;
    letter-spacing: 1px;
    text-align: center;
}
h1{
    font-size: 2em;
    margin: 0.67em 0;
}
input{
    width: 278px;
    height: 18px;
    margin-bottom: 10px;
    outline: none;
    padding: 10px;
    font-size: 13px;
    color: #fff;
    text-shadow:1px 1px 1px;
    border-top: 1px solid #312E3D;
    border-left: 1px solid #312E3D;
    border-right: 1px solid #312E3D;
    border-bottom: 1px solid #56536A;
    border-radius: 4px;
    background-color: #2D2D3F;
}
.but{
    width: 300px;
    min-height: 20px;
    display: block;
    background-color: #4a77d4;
    border: 1px solid #3762bc;
    color: #fff;
    padding: 9px 14px;
    font-size: 15px;
    line-height: normal;
    border-radius: 5px;
    margin: 0;
}


作者:Mind_programmonkey 发表于2017/11/13 17:22:54 原文链接
阅读:62 评论:0 查看评论

Mac 10.13 安装中文版 man 命令

$
0
0

Mac 10.13 安装中文版 man 命令

本文参考于 《Mac 安装man命令中文文档》,但原文提供的链接以及安装的版本比较老旧。因此重新整理新版在这边提供给大家。

为什么需要 man 以及 man 怎么使用

linux 或者 mac 系统的命令行工具非常多,可是我们不能记住所有的这些命令,通常只能记住一些我们常用的。遇到不常用的我们需要来查询一下这个命令是怎么使用的。这时候我们就需要使用到 man 命令了。

使用方法也非常简单,例如我们不清楚 ls 这个命令的使用方法,我们就可以在命令行中输入

man ls

来查看这个命令的详情。

但是默认情况下,输出的内容是英文的。可能很多英文不好的朋友希望有中文版本的 man ,这篇博文就是告诉大家,如何在 mac 上安装中文版本的的 man

至于 linux 系统则非常简单,查看 https://github.com/man-pages-zh/manpages-zh 中对应的版本,即可用简单的命令安装。

下载 manpages-zh 编辑安装

首先,我们打开上面的 github 地址,点击 releases 下载最新版本的 tar.gz 源码包。目前我下载到的是 1.6.3.2 版本的。

因为需要编译安装,所以你电脑上需要有编译工具,运行下面两个命令安装

brew install automake
brew install opencc

我这边是需要安装这两个编译工具,如果你下面编译出错,会提示你需要安装说明编辑工具的。利用 brew 安装即可。

如果你电脑没有安装 brew 工具,请参考 http://blog.csdn.net/FungLeo/article/details/57567538 这篇博文安装

好,准备工作做好,我们接着来。

# 进入下载目录
cd ~/Downloads/
# 下载最新版本的源码包
wget https://github.com/man-pages-zh/manpages-zh/archive/v1.6.3.2.tar.gz
# 解压源码包(atool命令,推荐安装这个工具,统一所有压缩文档的命令)
atool -x v1.6.3.2.tar.gz
# 或者使用这个命令解压
tar zxvf v1.6.3.2.tar.gz
# 进入源码包文件夹
cd manpages-zh-1.6.3.2/
# 编译安装 1
autoreconf --install --force
# 编译安装 2
./configure
# 编译安装 3
make
# 编译安装 4
sudo make install
# 配置别名
echo "alias cman='man -M /usr/local/share/man/zh_CN'" >> ~/.bash_profile
# 使别名生效
. ~/.bash_profile

这样,我们就安装上了中文版本的 man 工具了。我们可以使用

cman ls

来查看中文版本的解释了。但是由于 mac 上的 groff 工具比较老,所以中文会出现乱码。我们来解决一下这个问题。

安装 groff 新版本解决中文乱码的问题

首先,我们到 http://git.savannah.gnu.org/cgit/groff.git 这个页面下载 1.22 版本的 groff 安装包。我这边用命令行下载,你如果直接复制我的命令,不能下载,请到上面的地址去看看下载地址是否发生变化。

# 进入下载目录
cd ~/Downloads/
# 下载1.22版本的源码包
wget http://git.savannah.gnu.org/cgit/groff.git/snapshot/groff-1.22.tar.gz
# 解压
atool -x groff-1.22.tar.gz
# 进入目录
cd groff-1.22
# 编译安装
./configure
sudo make
sudo make install
# 添加配置
sudo vim /etc/man.conf

进入编辑之后,在文件末尾添加

NROFF preconv -e UTF8 | /usr/local/bin/nroff -Tutf8 -mandoc -c

最后 :wq 保存退出

然后,我们在输入

cman ls

就可以看到中文版本的命令介绍了。

本文由FungLeo原创,允许转载,但转载必须附注首发链接。谢谢。

作者:FungLeo 发表于2017/11/13 17:31:31 原文链接
阅读:197 评论:0 查看评论

自己学Docker:14.Docker使用实战:MySQL服务的创建

$
0
0

本文介绍如何使用docker迅速搭建MySQL的运行环境。

使用docker hub镜像

查找镜像

首先查找Docker Hub上的mysql镜像,有两种方式:
其一,直接登录docker hub网站https://hub.docker.com/搜索,可以知道镜像地址为https://hub.docker.com/_/mysql/
其二,可以使用docker search mysql命令搜索。
这里写图片描述

然后可以使用docker pull mysql命令下载相应的镜像,这里下载的是docker给出的官方MySQL镜像的最新版本。如果要pull指定版本,在后面加上:和版本号即可,比如:

docker pull mysql:5.6

这里写图片描述

使用镜像

找到需要的镜像后,就可以使用docker的run命令创建一个容器了。
比如,简单一点,使用默认配置的方法:

sudo docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql

其中:
-p 3306:3306:将容器的3306端口映射到主机的3306端口
-e 指定环境变量,其中MYSQL_ROOT_PASSWORD=root即初始化root用户的密码为root
-name 即命名容器的名称为mysql
-d: 后台运行容器,并返回容器ID
最后的mysql表明使用最新的mysql镜像,如果要指定版本,和pull命令一样,冒号:加版本号。
上面的命令如果本地不存在mysql镜像的话,也会先pull操作的。
这样就启动了一个运行mysql的docker容器。要连接的话,可以先查下启动容器的ip地址就好。
使用inspect命令,加上容器名称或者id即可:

sudo docker inspect mysql

这里写图片描述
可以看到我的容器ip地址为:172.17.0.2。
这时就可以使用mysql的客户端连接工具连接了。
这里写图片描述
这样,一个基本的mysql服务就搭建好了。省去了安装mysql的麻烦,基本上分分钟就可以完成。

sudo docker ps

可以看看容器的启动情况:
这里写图片描述

环境变量(Environment Variables)

除了上面的MYSQL_ROOT_PASSWORD一个强制填写的环境变量外,mysql容器还可以指定其他的可选环境变量。

  1. MYSQL_USER, MYSQL_PASSWORD:创建一个新用户并设置该用户的密码。需要注意的是,不要使用此机制来创建根超级用户,该用户默认使用由MYSQL_ROOT_PASSWORD变量指定的密码创建。
  2. MYSQL_DATABASE:指定在容器启动时要创建的数据库的名称。如果提供了用户/密码,那么该用户将被授予对该数据库的超级用户访问(对应于GRANT ALL)。
  3. MYSQL_ALLOW_EMPTY_PASSWORD:设置为yes以允许为root用户使用空密码启动容器。不建议设置为yes。
  4. MYSQL_RANDOM_ROOT_PASSWORD:设置为yes时将为root用户生成随机初始密码(使用pwgen)。生成的root密码将被打印到标准输出(如:GENERATED ROOT PASSWORD:…..)
  5. MYSQL_ONETIME_PASSWORD:一旦init完成,将root用户(不是在MYSQL_USER!中指定的用户)设置为过期,强制在第一次登录时更改密码。需要注意的是:该功能仅在MySQL 5.6+上受支持。在MySQL 5.5上使用这个选项会在初始化时引发一个适当的错误。

以上参数解释,具体可以参见docker hub上mysql镜像详情

配置文件

如果不想使用镜像默认的mysql配置文件,可以通过run命令的-v参数,指定文件挂载。这样也可以自己定义数据文件的存放位置等。
比如:

  • -v $PWD/conf/my.cnf:/etc/mysql/my.cnf:将主机当前目录下的conf/my.cnf挂载到容器的/etc/mysql/my.cnf
  • -v $PWD/logs:/logs:将主机当前目录下的logs目录挂载到容器的/logs
  • -v $PWD/data:/var/lib/mysql:将主机当前目录下的data目录挂载到容器的/var/lib/mysql,这个文件默认情况下为mysql的数据文件存放位置

具体仍可以参见docker hub上mysql镜像详情的最新更新。

通过 Dockerfile构建

除了使用官方提供的镜像,docker镜像都可以自己通过Dockerfile构建。
至于构建脚本,因目前水平有限,也写不出比官方更好的方式,这里给出docker hub上5.7版本的Dockerfile。可以自己通过docker build构建操作。完成后,使用的方法,和上面提到的是一样的。

源码位置:https://github.com/docker-library/mysql/blob/883703dfb30d9c197e0059a669c4bb64d55f6e0d/5.7/Dockerfile

FROM debian:jessie

# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd -r mysql && useradd -r -g mysql mysql

# add gosu for easy step-down from root
ENV GOSU_VERSION 1.7
RUN set -x \
    && apt-get update && apt-get install -y --no-install-recommends ca-certificates wget && rm -rf /var/lib/apt/lists/* \
    && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture)" \
    && wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$(dpkg --print-architecture).asc" \
    && export GNUPGHOME="$(mktemp -d)" \
    && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4 \
    && gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu \
    && rm -r "$GNUPGHOME" /usr/local/bin/gosu.asc \
    && chmod +x /usr/local/bin/gosu \
    && gosu nobody true \
    && apt-get purge -y --auto-remove ca-certificates wget

RUN mkdir /docker-entrypoint-initdb.d

RUN apt-get update && apt-get install -y --no-install-recommends \
# for MYSQL_RANDOM_ROOT_PASSWORD
        pwgen \
# for mysql_ssl_rsa_setup
        openssl \
# FATAL ERROR: please install the following Perl modules before executing /usr/local/mysql/scripts/mysql_install_db:
# File::Basename
# File::Copy
# Sys::Hostname
# Data::Dumper
        perl \
    && rm -rf /var/lib/apt/lists/*

RUN set -ex; \
# gpg: key 5072E1F5: public key "MySQL Release Engineering <mysql-build@oss.oracle.com>" imported
    key='A4A9406876FCBD3C456770C88C718D3B5072E1F5'; \
    export GNUPGHOME="$(mktemp -d)"; \
    gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \
    gpg --export "$key" > /etc/apt/trusted.gpg.d/mysql.gpg; \
    rm -r "$GNUPGHOME"; \
    apt-key list > /dev/null

ENV MYSQL_MAJOR 5.7
ENV MYSQL_VERSION 5.7.20-1debian8

RUN echo "deb http://repo.mysql.com/apt/debian/ jessie mysql-${MYSQL_MAJOR}" > /etc/apt/sources.list.d/mysql.list

# the "/var/lib/mysql" stuff here is because the mysql-server postinst doesn't have an explicit way to disable the mysql_install_db codepath besides having a database already "configured" (ie, stuff in /var/lib/mysql/mysql)
# also, we set debconf keys to make APT a little quieter
RUN { \
        echo mysql-community-server mysql-community-server/data-dir select ''; \
        echo mysql-community-server mysql-community-server/root-pass password ''; \
        echo mysql-community-server mysql-community-server/re-root-pass password ''; \
        echo mysql-community-server mysql-community-server/remove-test-db select false; \
    } | debconf-set-selections \
    && apt-get update && apt-get install -y mysql-server="${MYSQL_VERSION}" && rm -rf /var/lib/apt/lists/* \
    && rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql /var/run/mysqld \
    && chown -R mysql:mysql /var/lib/mysql /var/run/mysqld \
# ensure that /var/run/mysqld (used for socket and lock files) is writable regardless of the UID our mysqld instance ends up having at runtime
    && chmod 777 /var/run/mysqld \
# comment out a few problematic configuration values
    && find /etc/mysql/ -name '*.cnf' -print0 \
        | xargs -0 grep -lZE '^(bind-address|log)' \
        | xargs -rt -0 sed -Ei 's/^(bind-address|log)/#&/' \
# don't reverse lookup hostnames, they are usually another container
    && echo '[mysqld]\nskip-host-cache\nskip-name-resolve' > /etc/mysql/conf.d/docker.cnf

VOLUME /var/lib/mysql

COPY docker-entrypoint.sh /usr/local/bin/
RUN ln -s usr/local/bin/docker-entrypoint.sh /entrypoint.sh # backwards compat
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 3306
CMD ["mysqld"]
作者:Mungo 发表于2017/11/13 17:33:00 原文链接
阅读:159 评论:0 查看评论

移动端1px解决方案

$
0
0

理论知识:

1、以iphone6为例,375px宽。也就是说,假如你写一个div的css的width为375px,他就会撑满屏幕;

2、DPR:以iphone6为例,DPR为2。也即是说,他实际是在用4个像素(2x2)在显示你在css中写的1px像素点;

3、也就是说,iphone6虽然是375px宽,但是他实际x轴的像素点是750(375px * 2DPR);

4、那么假如你需要显示1px宽的线条,只需要写0.5px宽就行了(2个像素宽显示1px的css宽,那么1个像素显示0.5px的css宽);

5、理论上以上就解决了1px问题,但是等等,假如DPR为1或者3呢?总不可能对不同DPR的浏览器写不同的css样式吧;

6、那么initial-scale(缩放)就来解决这个问题了。initial-scale=1表示维持初始状态不变(但不写跟=1的效果是不同的);

7、initial-scale=0.5表示屏幕缩放到原来的50%宽和高,也就是说,原来屏幕可以显示375px的宽,现在可以显示750px的宽度了;

8、相当于0.5 * 2(这里的2是DPR的值)个屏幕像素,来表示1个CSS像素;

9、那么假如我css里写1px,并且想让屏幕用1px像素(也就是说,iphone6的375px像素里有750个像素点,我这里只想让他用1个像素点)来显示;

10、那么设置initial-scale = ( 1 / 屏幕的DPR值 ) 就可以了;

具体代码如下:

function resolve1px () {
  try {
    var dpr = window.devicePixelRatio
    if (!dpr) {
      return
    }
    var scale = 1 / dpr
    document.querySelector(`meta[name='viewport']`).setAttribute('content', `width=device-width, initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}, user-scalable=no`)
  } catch (err) {
    console.log('当前机型不支持1px解决方案')
  }
}

vw和rem:

1、vw是相对屏幕,所以本解决方案对于vw和vh不影响;

2、而rem如果取值时是相对于clientWidth来取值的话,也不影响,如果是针对其他的,那么可能会有影响(应该不会针对其他的吧);

3、rem的设置代码,应写于这里的代码之后,因为这里的代码会影响clientWidth的数值;

作者:qq20004604 发表于2017/11/13 17:41:07 原文链接
阅读:63 评论:0 查看评论

Unity Shader 学习笔记(6) 漫反射

$
0
0

Unity Shader 学习笔记(6) 漫反射

参考书籍:《Unity Shader 入门精要》
3D数学 学习笔记(8) 光照

逐顶点、逐像素、半兰伯特光照模型对比:


逐顶点光照(Lambert法则)

Shader "Custom/Chapter 6/DiffuseVertexLevel" {
    Properties {
        _Diffuse ("Diffuse",Color) = (1,1,1,1)
    }

    SubShader {
        Pass {
            Tags {"LightMode" = "ForwardBase"}              // 光照流水线,定义了才能得到一些内置光照变量

            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"                       // 后面用到 _LightColor0

            fixed4 _Diffuse;                                // 声明属性变量

            struct a2v {
                float4 vertex : POSITION;
                float3 normal : NORMAL; 
            };

            struct v2f {
                float4 pos : SV_POSITION;
                fixed3 color : COLOR0;
            };

            v2f vert(a2v v) {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;                          // 获得环境光,Unity内置变量

                // 等价 UnityObjectToWorldNormal(v.normal)
                fixed3 worldNormal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));    // 将法线从模型空间转到世界空间

                fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);                // 光源方向(假设场景只有一个光源且为平行光)

                // _LightColor0:光源颜色和强度。saturate:把参数限制在[0,1],CG提供的函数
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));

                o.color = ambient + diffuse;

                return o;
            }

            fixed4 frag(v2f i) : SV_Target {
                return fixed4(i.color,1.0);
            }

            ENDCG
        }
    }
    FallBack "Diffuse"
}

逐像素光照

原理同上,只不过是在片元着色器中计算。计算的法线是像素的法线。

struct v2f {
    float4 pos : SV_POSITION;
    fixed3 worldNormal : TEXCOORD0;             // 获取纹理坐标
};

v2f vert(a2v v) {
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);

    // 不需要计算光照模型,直接把世界空间法线传给着色器
    o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);    

    return o;
}

fixed4 frag(v2f i) : SV_Target {
    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;                  // 获取环境光

    fixed3 worldNormal = normalize(i.worldNormal);                  // 获取世界坐标的法线

    fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);        // 光源方向

    // _LightColor0:光源颜色和强度,saturate:把参数限制在[0,1]
    fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLight));

    fixed3 color = ambient + diffuse;

    return fixed4(color,1.0);
}

半兰伯特光照

因为上面两种光照在背面都是无法接收光照而显示全黑,所有就有了这个模型。定义如下:

一般α和β都取0.5,也就是把原来( n · I )的范围从[-1, 1]映射到[0, 1],即法线平行光照的片元才会是全黑的。

从逐片元光照代码中直接修改公式即可。

// 将原本n·l的[-1,1]映射到[0,1],两个常量可以变,通常都是两个0.5
fixed halfLambert = dot(worldNormal,worldLight) * 0.5 + 0.5;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
作者:l773575310 发表于2017/11/13 17:52:36 原文链接
阅读:126 评论:0 查看评论

nginx: [emerg] "server" directive is not allowed here in /usr/local/nginx/conf/nginx.conf:45

$
0
0

问题描述 :今天修改 了下 nginx的配置文件 ,结果想重新启动 nginx时 报错


我就怀疑是 nginx.conf弄出问题了 我又检查一下

http
{
 server{}
}
我少了个}  添加上问题解决

作者:drdongshiye 发表于2017/11/13 17:52:53 原文链接
阅读:160 评论:0 查看评论

开源插件ACTable已经打包放到Maven中央仓库啦!

$
0
0

开源插件ACTable(基于Mybatis开发的增强插件,实现自动建表、并支持共通CUDR)上线啦!

由于很多小伙伴在使用actable的时候都不得不下载源码,然后在本地install打包然后使用,所以最近我抽空,将其打包放在了maven中央仓库上,因此包的结构有所调整,引入方式如下:

pom.xml中引入

<dependency>
    <groupId>com.gitee.sunchenbin.mybatis.actable</groupId>
    <artifactId>mybatis-enhance-actable</artifactId>
    <version>1.0.1</version>
</dependency>

包名调整后的配置文档详情请参考该地址:https://gitee.com/sunchenbin/mybatis-enhance

作者:sun5769675 发表于2017/11/13 18:02:02 原文链接
阅读:220 评论:0 查看评论

LWC 58:726. Number of Atoms

$
0
0

LWC 58:726. Number of Atoms

传送门:726. Number of Atoms

Problem:

Given a chemical formula (given as a string), return the count of each atom.

An atomic element always starts with an uppercase character, then zero or more lowercase letters, representing the name.

1 or more digits representing the count of that element may follow if the count is greater than 1. If the count is 1, no digits will follow. For example, H2O and H2O2 are possible, but H1O2 is impossible.

Two formulas concatenated together produce another formula. For example, H2O2He3Mg4 is also a formula.

A formula placed in parentheses, and a count (optionally added) is also a formula. For example, (H2O2) and (H2O2)3 are formulas.

Given a formula, output the count of all elements as a string in the following form: the first name (in sorted order), followed by its count (if that count is more than 1), followed by the second name (in sorted order), followed by its count (if that count is more than 1), and so on.

Example 1:

Input:
formula = “H2O”
Output: “H2O”
Explanation:
The count of elements are {‘H’: 2, ‘O’: 1}.

Example 2:

Input:
formula = “Mg(OH)2”
Output: “H2MgO2”
Explanation:
The count of elements are {‘H’: 2, ‘Mg’: 1, ‘O’: 2}.

Example 3:

Input:
formula = “K4(ON(SO3)2)2”
Output: “K4N2O14S4”
Explanation:
The count of elements are {‘K’: 4, ‘N’: 2, ‘O’: 14, ‘S’: 4}.

Note:

  • All atom names consist of lowercase letters, except for the first character which is uppercase.
  • The length of formula will be in the range [1, 1000].
  • Formula will only consist of letters, digits, and round parentheses, and is a valid formula as defined in the problem.

思路:
递归真的是个好东西,思路比较暴力,如果没有遇到括号,则按照传统的检测字符和数字进行更新即可。如果检测到括号,则先得到括号内的元素和个数的映射,接着乘上大括号外的第一个num,再将第三部分的答案组合进来即可。

比如:

H2O2(SO2)23(OH)23

上述字符串可以组成三部分

lf = "H2O2"
mid = "(SO2)23" 
rt = "(OH)23"

只有mid需要特殊处理,把{s = 1 * 32, O = 2 * 23 = 46}
而一旦解决mid,则rt跟着被解决,lf在之前就被解决了,具体看代码,不解释了。

代码如下:

    public String countOfAtoms(String formula) {
        Map<String, Integer> map = go(formula.toCharArray(), 0, formula.length() - 1);

        StringBuilder sb = new StringBuilder();
        List<String> set = new ArrayList<>(map.keySet());
        Collections.sort(set);
        for (String key : set) {
            sb.append(key);
            if (map.get(key) > 1) {
                sb.append(map.get(key));
            }
        }
        return sb.toString();
    }

    public Map<String, Integer> go(char[] cs, int s, int e){
        if (s > e) return new HashMap<>();

        Map<String, Integer> ans = new HashMap<>();
        int idx = new String(cs).substring(s, e + 1).indexOf("(");
        if (idx != -1) idx += s;
        if (idx == -1) {

            StringBuilder sb = new StringBuilder();
            for (int i = s; i <= e;) {
                if (Character.isUpperCase(cs[i])) {
                    sb.append(cs[i]);
                    i ++;

                    if (i <= e && Character.isUpperCase(cs[i])) {
                        String ele = sb.toString();
                        if (!ans.containsKey(ele)) {
                            ans.put(ele, 1);
                        }
                        else {
                            ans.put(ele, ans.get(ele) + 1);
                        }
                        sb = new StringBuilder();
                    }
                    else if (i <= e && Character.isLowerCase(cs[i])) {
                        while (i <= e && Character.isLowerCase(cs[i])) {
                            sb.append(cs[i]);
                            i ++;
                        }
                        if (i <= e && !Character.isDigit(cs[i])){
                            String ele = sb.toString();
                            if (!ans.containsKey(ele)) {
                                ans.put(ele, 1);
                            }
                            else {
                                ans.put(ele, ans.get(ele) + 1);
                            }
                            sb = new StringBuilder();
                        }
                    }

                    if (i <= e && Character.isDigit(cs[i])) {
                        int num = 0;
                        while (i <= e && Character.isDigit(cs[i])) {
                            num = num * 10 + cs[i] - '0';
                            i ++;
                        }

                        String ele = sb.toString();
                        if (!ans.containsKey(ele)) {
                            ans.put(ele, num);
                        }
                        else {
                            ans.put(ele, ans.get(ele) + num);
                        }

                        sb = new StringBuilder();
                    }
                }
            }

            String ele = sb.toString();
            if (!ele.isEmpty()) {
                if (!ans.containsKey(ele)) {
                    ans.put(ele, 1);
                }
                else {
                    ans.put(ele, ans.get(ele) + 1);
                }
            }
            return ans;
        }
        else {
            Map<String, Integer> lf = go(cs, s, idx - 1);

            StringBuilder sb = new StringBuilder();
            int p = 1;
            Map<String, Integer> include;
            int num = 0;
            int j = -1;
            for (int i = idx + 1; i <= e; ++i) {
                sb.append(cs[i]);
                if (cs[i] == '(') {
                    p ++;
                }
                else if (cs[i] == ')') {
                    p --;
                    if (p == 0) {
                        j = i + 1;
                        while (j <= e && Character.isDigit(cs[j])) {
                            num = num * 10 + cs[j] - '0';
                            j ++;
                        }
                        break;
                    }
                }
            }
            String ele = sb.toString().substring(0, sb.length() - 1);
            include = go(ele.toCharArray(), 0, ele.length() - 1);

            for (String key : include.keySet()) {
                include.put(key, include.get(key) * num);
            }

            Map<String, Integer> rt = go(cs, j, e);

            add(ans, lf);
            add(ans, include);
            add(ans, rt);

            return ans;
        }
    }

    public void add(Map<String, Integer> ans, Map<String, Integer> tmp) {
        for (String key : tmp.keySet()) {
            if (!ans.containsKey(key)) {
                ans.put(key, tmp.get(key));
            }
            else {
                ans.put(key, ans.get(key) + tmp.get(key));
            }
        }
    }

注意一些细节:
首先,对于字符串”H2O2BeBe49”这样的formula,第一次一定会遇到大写字符,接着把大写字母后的小写字母全部给append上,这样只会遇到两种情况:下一个字符为数字,或者下一个字符为大写字符。

如过遇到字符,则计算num,如果遇到大写字符,则formula省略了1,需要加上。

代码更新如下:

    public String countOfAtoms(String formula) {
        Map<String, Integer> map = go(formula.toCharArray(), 0, formula.length() - 1);

        StringBuilder sb = new StringBuilder();
        List<String> set = new ArrayList<>(map.keySet());
        Collections.sort(set);
        for (String key : set) {
            sb.append(key);
            if (map.get(key) > 1) {
                sb.append(map.get(key));
            }
        }
        return sb.toString();
    }

    public Map<String, Integer> go(char[] cs, int s, int e){
        if (s > e) return new HashMap<>();

        Map<String, Integer> ans = new HashMap<>();
        int idx = new String(cs).substring(s, e + 1).indexOf("(");
        if (idx != -1) idx += s;
        if (idx == -1) {

            StringBuilder sb = new StringBuilder();
            for (int i = s; i <= e;) {
                if (Character.isUpperCase(cs[i])) {
                    sb.append(cs[i]);
                    i ++;

                    while (i <= e && Character.isLowerCase(cs[i])){
                        sb.append(cs[i]);
                        i ++;
                    }

                    if (i <= e && !Character.isDigit(cs[i])){
                        String ele = sb.toString();
                        if (!ans.containsKey(ele)) {
                            ans.put(ele, 1);
                        }
                        else {
                            ans.put(ele, ans.get(ele) + 1);
                        }
                        sb = new StringBuilder();
                    }
                    else if (i <= e && Character.isDigit(cs[i])) {

                        int num = 0;
                        while (i <= e && Character.isDigit(cs[i])) {
                            num = num * 10 + cs[i] - '0';
                            i ++;
                        }

                        String ele = sb.toString();
                        if (!ans.containsKey(ele)) {
                            ans.put(ele, num);
                        }
                        else {
                            ans.put(ele, ans.get(ele) + num);
                        }

                        sb = new StringBuilder();
                    }
                }
            }

            String ele = sb.toString();
            if (!ele.isEmpty()) {
                if (!ans.containsKey(ele)) {
                    ans.put(ele, 1);
                }
                else {
                    ans.put(ele, ans.get(ele) + 1);
                }
            }
            return ans;
        }
        else {
            Map<String, Integer> lf = go(cs, s, idx - 1);

            StringBuilder sb = new StringBuilder();
            int p = 1;
            Map<String, Integer> include;
            int num = 0;
            int j = -1;
            for (int i = idx + 1; i <= e; ++i) {
                sb.append(cs[i]);
                if (cs[i] == '(') {
                    p ++;
                }
                else if (cs[i] == ')') {
                    p --;
                    if (p == 0) {
                        j = i + 1;
                        while (j <= e && Character.isDigit(cs[j])) {
                            num = num * 10 + cs[j] - '0';
                            j ++;
                        }
                        break;
                    }
                }
            }
            String ele = sb.toString().substring(0, sb.length() - 1);
            include = go(ele.toCharArray(), 0, ele.length() - 1);

            for (String key : include.keySet()) {
                include.put(key, include.get(key) * num);
            }

            Map<String, Integer> rt = go(cs, j, e);
            add(ans, lf);
            add(ans, include);
            add(ans, rt);

            return ans;
        }
    }

    public void add(Map<String, Integer> ans, Map<String, Integer> tmp) {
        for (String key : tmp.keySet()) {
            if (!ans.containsKey(key)) {
                ans.put(key, tmp.get(key));
            }
            else {
                ans.put(key, ans.get(key) + tmp.get(key));
            }
        }
    }
作者:u014688145 发表于2017/11/13 18:12:01 原文链接
阅读:146 评论:0 查看评论

leetcode题解-686. Repeated String Match && 38. Count and Say

$
0
0

先看686这道题目:

Given two strings A and B, find the minimum number of times A has to be repeated such that B is a substring of it. If no such solution, return -1.

For example, with A = "abcd" and B = "cdabcdab".

Return 3, because by repeating A three times (“abcdabcdabcd”), B is a substring of it; and B is not a substring of A repeated two times ("abcdabcd").

Note:
The length of A and B will be between 1 and 10000.

给了两个字符串A和B,需要我们找出A重复多少次才能包含B这个子串,最简单的思路就是不断地重复A,直到A包含了B,或者A的长度已经足够大,即便再添加也没有什么意义,这时就说明A无法包含B,直接返回-1即可。很容易想到,当A的长度大于B时也就没有了添加的必要。代码如当下所示:

    public int repeatedStringMatch(String A, String B) {
        StringBuilder res = new StringBuilder(A);
        int count = 1;
        while(res.indexOf(B) < 0){
            //不断添加A,直到res的长度大于B,这时如果res包含B,则返回添加次数,否则说明无法包含,返回-1。
            //这里使用res的长度大于A+B的长度,是因为如果一开始A就比B大的话,不进行这一步判断会出错。比如A="abababaaba",B="aabaaba"
            if(res.length() - A.length() > B.length()) return -1;
            res.append(A);
            count ++;
        }
        return count;
    }

手上面这个例子启发,其实我们可以把情况分成两种,第一种是A长度大于B,那么直接使用A或者A+A判断是否包含B即可,然后如果A长度小于B,在使用上面的方法进行拼接判断,效率有很大提升,可以击败88%的用户:

    //88%
    public int repeatedStringMatch2(String A, String B) {
        if(A.length() > B.length()){
            if(A.contains(B))
                return 1;
            else if((A+A).contains(B))
                return 2;
        }
        StringBuilder res = new StringBuilder(A);
        int count = 1;
        while(res.indexOf(B) < 0){
            if(res.length() > B.length()) return -1;
            res.append(A);
            count ++;
        }
        return count;
    }

此外,我还在discuss里面发现一种效率更高的解法,可以击败99%的用户,代码如下所示:

    //99%
    public int repeatedStringMatch1(String A, String B) {
        int count = 1;
        int i = 0;
        for (int j = 0; j < B.length(); j++) {
            if (A.charAt(i) != B.charAt(j)) {
                if (count > 1) {       // already second time: no way to make B from A
                    return -1;
                }
                j = -1;    // try to match j's starting character with next i
            }

            i++;
            if (i == A.length()) {        // one more time of A
                if (j == B.length() - 1) {
                    break;
                }
                count++;
                i = 0;
                }
        }
        return count;
    }

接下来看第二道题目:

The count-and-say sequence is the sequence of integers with the first five terms as following:

1.     1
2.     11
3.     21
4.     1211
5.     111221
1 is read off as "one 1" or 11.
11 is read off as "two 1s" or 21.
21 is read off as "one 2, then one 1" or 1211.
Given an integer n, generate the nth term of the count-and-say sequence.

Note: Each term of the sequence of integers will be represented as a string.

Example 1:

Input: 1
Output: "1"
Example 2:

Input: 4
Output: "1211"

要获得第n个字符串的读法,而读法就是有m个数字n,就读mn,这样以此类推生成字符串。所以我们只需要递推生成字符串在获得其读法,然后一次生成读取即可。代码如下所示:

    //65%
    public String countAndSay(int n) {
        StringBuilder curr=new StringBuilder("1");
        StringBuilder prev;
        int count;
        char say;
        for (int i=1;i<n;i++){
            prev=curr;
            curr=new StringBuilder();
            //分别用于统计和记录数字的个数和数字
            count=1;
            say=prev.charAt(0);

            for (int j=1,len=prev.length();j<len;j++){
                if (prev.charAt(j)!=say){
                    curr.append(count).append(say);
                    count=1;
                    say=prev.charAt(j);
                }
                else count++;
            }
            curr.append(count).append(say);
        }
        return curr.toString();

    }
作者:liuchonge 发表于2017/11/13 19:57:45 原文链接
阅读:224 评论:0 查看评论

安卓intent发起广播事件给系统或当前app,并从系统或当前app中接收广播

$
0
0

全栈工程师开发手册 (作者:栾鹏)

python教程全解

定义广播接收器

这里定义了一个广播接收一个系统屏幕选装的广播事件和一个自定义事件


import android.content.Intent;
import android.net.Uri;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.util.Log;

public class BroadcastReceiver1 extends BroadcastReceiver {

    public final static String key1 = "str_key";
    public final static String key2 = "double_key";
    public static final String action_name = "com.lp.intent.name1";
      @Override
      public void onReceive(Context context, Intent intent) {
          //获取事件名称
          String action_name = intent.getAction();
          Log.v("广播接收", "事件名称:"+action_name);
          if (action_name.equals("android.net.conn.CONNECTIVITY_CHANGE")){
                Log.v("广播接收","网络状态发生了变化");
          }

          if (action_name.equals("com.lp.intent.name1")) {
              //从intent中获得lifeform的细节
              Uri data = intent.getData();
              String str_value = intent.getStringExtra(key1);
              double double_value = intent.getDoubleExtra(key2, 0);
              if (str_value.equals("value1")) {
                  Log.v("广播接收",str_value);
              }
          }

      }
}

注册广播接收器

方法1:通过AndroidManifest.xml文件注册

在AndroidManifest.xml文件中注册接收处理系统广播、自定义广播的广播接收器。不同的广播接收事件还需要不同的权限,这里需要添加网络权限

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>


<!-- 注册BroadcastReceiver,这种注册方式当应用程序未启动或被终止,也能接收  -->
  <receiver android:name="com.lp.app.intent.BroadcastReceiver1">
       <intent-filter>
               <!--  intent-filter指定要监听的动作字符串,这里添加所有(category)网络连接事件和一个(action)自定义事件 -->
                <category android:name="android.net.conn" />
                <action android:name="com.lp.intent.name1" />
       </intent-filter>
   </receiver> 

方法2:在窗口中动态注册广播接收

  //使用Intent来广播事件。参数标准一般设置在接收文件中。
    private IntentFilter filter = new IntentFilter("com.lp.intent.name1");   //IntentFilter用来设置能够触发广播接收的事件信息。初始化时可以添加一个监听事件名称
    private BroadcastReceiver1 receiver = new BroadcastReceiver1();

    //在窗口运行时才启动接收器,便于更新UI的广播器
    @Override
    public synchronized void onResume() {
      super.onResume();
      //注册广播接收,也可以在manifest中注册
      filter.addCategory("android.net.conn");   //addCategory添加一系列监听事件
      filter.addCategory("android.net.conn.CONNECTIVITY_CHANGE");   //添加一个监听事件,网络变化事件
      registerReceiver(receiver, filter);    //参数为接收处理器,和触发接收机的条件。
      Log.v("广播", "注册了广播接收");
      super.onResume();
    }

    @Override
    public synchronized void onPause() {
      //取消广播接收
      unregisterReceiver(receiver);  
      Log.v("广播", "注销了广播接收");
      super.onPause();
    }

发起广播事件给系统

当然你只能发起自定义广播事件给系统其它应用或当前应用

private void Broadcastfun() {
    //下面的事件名称和添加属性,我们习惯使用接收器中的常量,是为了发送和接收两端出现不一致。
    Intent intent = new Intent(BroadcastReceiver1.action_name);  //参数为intent事件名称字符串,用来标识事件唯一性,习惯上使用与包名相同格式
    intent.putExtra(BroadcastReceiver1.key1,"value1");      //传递字符串数据
    intent.putExtra(BroadcastReceiver1.key2,23.5);          //传递浮点数据
    sendBroadcast(intent);
    Log.v("广播", "发起系统广播");
 }

注册定义局部广播接收器

LocalBroadcastManager lbm=LocalBroadcastManager.getInstance(this);
lbm.registerReceiver(new BroadcastReceiver(){
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.v("局部广播", "接收到局部消息");
    }
}, new IntentFilter("action_name1"));

发送局部广播

LocalBroadcastManager lbm=LocalBroadcastManager.getInstance(this);
//发送局部广播事件
LocalBroadcastManager lbm1=LocalBroadcastManager.getInstance(this);
lbm1.sendBroadcast(new Intent("action_name1"));

其他

另外广播接收还有
广播有序Intent,sendOrderedBroadcast
广播Sticky Intent,sendStickyBroadcast
Pending Intent,响应将来事件时触发的intent

作者:luanpeng825485697 发表于2017/11/13 20:28:00 原文链接
阅读:140 评论:0 查看评论

移动Web开发基础-比例盒子

$
0
0

前言

上篇在移动Web开发基础-百分比+flex布局方案中说到了比例盒子的实现,因为在移动端,需要适应各种屏幕宽度的设备,所以我们的图片以及其他一些元素需要根据屏幕宽度自适应的等比例缩放,这里就需要用到比例盒子的布局方法。接下来让我们一起来了解下实现比例盒子的几种方法吧!

这里会介绍四种实现方式,分别是用css的vw单位,垂直方向的padding或者margin,以及因此产生的问题而衍生的用伪元素实现的解决方法。

注意:按照规定,margin, padding 的百分比数值是相对 父元素宽度 的宽度计算的。

CSS 单位 VW

宽度高度都用同一个单位VW,其他比例也是相应的计算元素宽高占屏幕宽度的百分比。

<div class="vw"></div>

.vw{
    width: 20vw;
    height: 20vw;
    background-color: #000;
}

垂直方向的padding+absolute

垂直方向的padding(padding-top/padding-bottom),因为是用padding撑开的,所以子元素会跑到区域外,所以该容器要相对定位,子元素一般全部绝对定位在里面。

//因为必须要有父元素的宽度作为计算的参照,所以添加了一个容器,
开发时自己清楚父元素的宽度就行,一般父元素100%宽度比较方便计算。

<div class="padding-wp">
    <div class="padding"></div>
</div>

.padding-wp{
    width: 20%;
}
.padding{
    position: relative;/* 子元素要绝对定位 */
    width: 100%;
    padding-top: 100%;
    max-height: 100px; /* 不起作用 */
    overflow: hidden;
    background-color: yellow;
}

看到这里写了max-height 不起作用,是因为 max-height 属性只限制于 height,也就是只会对元素的 content height 起作用,那么我们是不是能用一个子元素撑开 content 部分的高度,从而使 max-height 属性生效呢?于是我们就用伪元素的方式来实现。

垂直方向的padding+absolute+ :after

<div class="padding-wp-after">
    <div class="padding-after"></div>
</div>

.padding-wp-after{
    width: 20%;
}
.padding-after{
    position: relative;/* 子元素要绝对定位 */
    width: 100%;
    max-height: 100px; /* 起作用 */
    overflow: hidden;
    background-color: blue;
}
.padding-after:after{
    content: "";
    display: block;
    padding-top: 100%;
}

这里我们就能看到max-height起作用了。


这里写图片描述

垂直方向的margin+absolute+ :after

<div class="margin-wp-after">
    <div class="margin-after"></div>
</div>

.margin-wp-after{
    width: 20%;
}
.margin-after{
    width: 100%;
    max-height: 100px; /* 起作用 */
    background-color: #000;
    overflow: hidden;
}
.margin-after:after{
    content: "";
    display: block;
    margin-top: 100%;
}

这里用伪元素的margin-top来撑开了父容器的空间,但是会发现父容器多了一个属性overflow: hidden;这是因为要触发元素的BFC属性,不然margin会有穿透的问题。

总结

本文介绍了几种实现比例盒子的方法,不对相信看完此文的你应该也知道了以上方法的利弊,方法一的兼容性不言而喻,方法二会有max-height的问题,方法四margin经常会有穿透,重叠,对上下文影响比较大也不推荐,所以,你应该猜到了,我推荐方法三!用伪元素以及定位的方法实现,如果你用了less/sass这样的CSS预编译语言,那你就可以写一个比例盒子的混淆,方便在其他地方调用。


本文DEMO codepen地址 https://codepen.io/xiangshuo1992/pen/ZayEYZ

本文参考:
纯 CSS 实现自适应正方形

有兴趣你还可以了解更多
CSS实现长宽比的几种方案

作者:u013778905 发表于2017/11/13 21:05:06 原文链接
阅读:125 评论:0 查看评论

Angular 4入门教程系列:14:PrimeNG的使用方式

$
0
0

这篇文章介绍一下Angular的老牌UI组件库PrimeNG,并演示一下如何使引入PrimeNG到项目之中。

Why PrimeNG

使用PrimeNG有很多原因,比如

  • 70多个完善的组件
  • 开源
  • 提高生产性
  • 多种主题
  • 高度可定制的模板
  • 移动端用户体验的增强

PrimeNG网址

事前准备

安装node

详细可以参照:http://blog.csdn.net/liumiaocn/article/details/78510679

[root@angular ~]# npm -v
5.5.1
[root@angular ~]# 
[root@angular ~]# node -v
v9.1.0
[root@angular ~]#

安装angular-cli

安装命令:npm install -g @angular/cli –unsafe-perm

[root@angular ~]# npm install -g @angular/cli --unsafe-perm
/usr/local/npm/node/bin/ng -> /usr/local/npm/node/lib/node_modules/@angular/cli/bin/ng

> node-sass@4.6.1 install /usr/local/npm/node/lib/node_modules/@angular/cli/node_modules/node-sass
> node scripts/install.js

Downloading binary from https://github.com/sass/node-sass/releases/download/v4.6.1/linux-x64-59_binding.node
Download complete  ] - :
Binary saved to /usr/local/npm/node/lib/node_modules/@angular/cli/node_modules/node-sass/vendor/linux-x64-59/binding.node
Caching binary to /root/.npm/node-sass/4.6.1/linux-x64-59_binding.node

> node-sass@4.6.1 postinstall /usr/local/npm/node/lib/node_modules/@angular/cli/node_modules/node-sass
> node scripts/build.js

Binary found at /usr/local/npm/node/lib/node_modules/@angular/cli/node_modules/node-sass/vendor/linux-x64-59/binding.node
Testing binary
Binary is fine
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.1.3 (node_modules/@angular/cli/node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.1.3: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})

+ @angular/cli@1.5.0
added 148 packages and updated 1 package in 122.371s
[root@angular ~]#

设定link

[root@angular ~]# ln -s /usr/local/npm/node/lib/node_modules/@angular/cli/bin/ng /usr/local/bin/ng
[root@angular ~]# which ng
/usr/local/bin/ng
[root@angular ~]# ng version

    _                      _                 ____ _     ___
   / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
  / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
 / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
/_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
               |___/

Angular CLI: 1.5.0
Node: 9.1.0
OS: linux x64
Angular: 
...
[root@angular ~]# 

安装typescript

[root@angular ~]# npm install -g typescript
/usr/local/npm/node/bin/tsc -> /usr/local/npm/node/lib/node_modules/typescript/bin/tsc
/usr/local/npm/node/bin/tsserver -> /usr/local/npm/node/lib/node_modules/typescript/bin/tsserver
+ typescript@2.6.1
updated 1 package in 9.327s
[root@angular ~]# 

创建cli项目骨架

[root@angular ~]# ng new PrimengProject
  create PrimengProject/README.md (1030 bytes)
  create PrimengProject/.angular-cli.json (1250 bytes)
  create PrimengProject/.editorconfig (245 bytes)
  create PrimengProject/.gitignore (516 bytes)
  create PrimengProject/src/assets/.gitkeep (0 bytes)
  create PrimengProject/src/environments/environment.prod.ts (51 bytes)
  create PrimengProject/src/environments/environment.ts (387 bytes)
  create PrimengProject/src/favicon.ico (5430 bytes)
  create PrimengProject/src/index.html (301 bytes)
  create PrimengProject/src/main.ts (370 bytes)
  create PrimengProject/src/polyfills.ts (2667 bytes)
  create PrimengProject/src/styles.css (80 bytes)
  create PrimengProject/src/test.ts (1085 bytes)
  create PrimengProject/src/tsconfig.app.json (211 bytes)
  create PrimengProject/src/tsconfig.spec.json (304 bytes)
  create PrimengProject/src/typings.d.ts (104 bytes)
  create PrimengProject/e2e/app.e2e-spec.ts (297 bytes)
  create PrimengProject/e2e/app.po.ts (208 bytes)
  create PrimengProject/e2e/tsconfig.e2e.json (235 bytes)
  create PrimengProject/karma.conf.js (923 bytes)
  create PrimengProject/package.json (1320 bytes)
  create PrimengProject/protractor.conf.js (722 bytes)
  create PrimengProject/tsconfig.json (363 bytes)
  create PrimengProject/tslint.json (2985 bytes)
  create PrimengProject/src/app/app.module.ts (316 bytes)
  create PrimengProject/src/app/app.component.css (0 bytes)
  create PrimengProject/src/app/app.component.html (1120 bytes)
  create PrimengProject/src/app/app.component.spec.ts (986 bytes)
  create PrimengProject/src/app/app.component.ts (207 bytes)
Installing packages for tooling via npm.
Installed packages for tooling via npm.
Project 'PrimengProject' successfully created.
[root@angular ~]# 

确认结果

[root@angular ~]# cd PrimengProject/
[root@angular PrimengProject]# ng serve -H 0.0.0.0 --open
** NG Live Development Server is listening on 0.0.0.0:4200, open your browser on http://localhost:4200/ **
...
chunk {vendor} vendor.bundle.js (vendor) 7.02 MB [initial] [rendered]

webpack: Compiled successfully.

这里写图片描述

安装primeng

[root@angular PrimengProject]# npm install primeng --save-dev --unsafe-perm

> node-sass@4.6.1 install /root/PrimengProject/node_modules/node-sass
> node scripts/install.js

Cached binary found at /root/.npm/node-sass/4.6.1/linux-x64-59_binding.node

> node-sass@4.6.1 postinstall /root/PrimengProject/node_modules/node-sass
> node scripts/build.js

Binary found at /root/PrimengProject/node_modules/node-sass/vendor/linux-x64-59/binding.node
Testing binary
Binary is fine
npm WARN codelyzer@3.2.2 requires a peer of @angular/compiler@^2.3.1 || >=4.0.0-beta <5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN codelyzer@3.2.2 requires a peer of @angular/core@^2.3.1 || >=4.0.0-beta <5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.1.3 (node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.1.3: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})

+ primeng@5.0.0-rc.0
added 148 packages and updated 1 package in 61.238s
[root@angular PrimengProject]# 

安装font-awesome

[root@angular PrimengProject]# npm install font-awesome
npm WARN codelyzer@3.2.2 requires a peer of @angular/compiler@^2.3.1 || >=4.0.0-beta <5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN codelyzer@3.2.2 requires a peer of @angular/core@^2.3.1 || >=4.0.0-beta <5.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.1.3 (node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.1.3: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})

+ font-awesome@4.7.0
added 116 packages in 34.943s
[root@angular PrimengProject]#

设定.angular-cli.json

如下,设定font-awesome和primeng相关的css:

      "styles": [
        "styles.css",
        "../node_modules/primeng/resources/themes/omega/theme.css",
        "../node_modules/primeng/resources/primeng.min.css",
        "../node_modules/font-awesome/css/font-awesome.css"
      ],

修改HTML模板

修改app.component.html

[root@angular app]# cat app.component.html 
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
  <h1>
    Welcome to {{title}}!
  </h1>
</div>

<h3 class="first">Addons</h3>
<div class="ui-g ui-fluid">
    <div class="ui-g-12 ui-md-4">
        <div class="ui-inputgroup">
            <span class="ui-inputgroup-addon"><i class="fa fa-user"></i></span>
            <input type="text" pInputText placeholder="Username">         
        </div>
    </div>

    <div class="ui-g-12 ui-md-4">
        <div class="ui-inputgroup">
            <span class="ui-inputgroup-addon">$</span>
            <input type="text" pInputText placeholder="Price">   
            <span class="ui-inputgroup-addon">.00</span>      
        </div>
    </div>

    <div class="ui-g-12 ui-md-4">
        <div class="ui-inputgroup">
            <span class="ui-inputgroup-addon">www</span>
            <input type="text" pInputText placeholder="Website">      
        </div>
    </div>
</div>

<h3>Multiple Addons</h3>
<div class="ui-g">
    <div class="ui-g-12">
        <div class="ui-inputgroup">
            <span class="ui-inputgroup-addon"><i class="fa fa-credit-card"></i></span>  
            <span class="ui-inputgroup-addon"><i class="fa fa-cc-visa"></i></span>   
            <input type="text" pInputText placeholder="Price"> 
            <span class="ui-inputgroup-addon">$</span>  
            <span class="ui-inputgroup-addon">.00</span>      
        </div>
    </div>
</div>

[root@angular app]#

结果确认

这里写图片描述

总结

这篇文章介绍了如何在项目中使用PrimeNG。

作者:liumiaocn 发表于2017/11/13 21:47:00 原文链接
阅读:150 评论:0 查看评论

机器学习:决策树--python

$
0
0

今天,我们介绍机器学习里比较常用的一种分类算法,决策树。决策树是对人类认知识别的一种模拟,给你一堆看似杂乱无章的数据,如何用尽可能少的特征,对这些数据进行有效的分类。

决策树借助了一种层级分类的概念,每一次都选择一个区分性最好的特征进行分类,对于可以直接给出标签 label 的数据,可能最初选择的几个特征就能很好地进行区分,有些数据可能需要更多的特征,所以决策树的深度也就表示了你需要选择的几种特征。

在进行特征选择的时候,常常需要借助信息论的概念,利用最大熵原则。
决策树一般是用来对离散数据进行分类的,对于连续数据,可以事先对其离散化。

在介绍决策树之前,我们先简单的介绍一下信息熵,我们知道,熵的定义为:

En(xi)=log2p(xi)

p(xi) 表示 x 属于第 i 类的概率,我们把所有类的期望定义为熵:

H=i=1np(xi)log2p(xi)

这里 n 表示类别的个数。

我们先构造一些简单的数据:

from sklearn import datasets
import numpy as np
import matplotlib.pyplot as plt
import math
import operator

def Create_data():
    dataset = [[1, 1, 'yes'],
               [1, 1, 'yes'],
               [1, 0, 'no'],
               [0, 1, 'no'],
               [0, 1, 'no'],
               [3, 0, 'maybe']]
    feat_name = ['no surfacing', 'flippers']
    return dataset, feat_name

然后定义一个计算熵的函数:

def Cal_entrpy(dataset):
    n_sample = len(dataset)
    n_label = {}
    for featvec in dataset:
        current_label = featvec[-1]
        if current_label not in n_label.keys():
            n_label[current_label] = 0
        n_label[current_label] += 1
    shannonEnt = 0.0
    for key in n_label:
        prob = float(n_label[key]) / n_sample
        shannonEnt -= prob * math.log(prob, 2)

    return shannonEnt

要注意的是,熵越大,说明数据的类别越分散,越呈现某种无序的状态。

下面再定义一个拆分数据集的函数:

def Split_dataset(dataset, axis, value):
    retDataSet = []
    for featVec in dataset:
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis]
            reducedFeatVec.extend(featVec[axis+1 :])
            retDataSet.append(reducedFeatVec)

    return retDataSet

结合前面的几个函数,我们可以构造一个特征选择的函数:

def Choose_feature(dataset):
    num_sample = len(dataset)
    num_feature = len(dataset[0]) - 1
    baseEntrpy = Cal_entrpy(dataset)
    best_Infogain = 0.0
    bestFeat = -1
    for i in range (num_feature):
        featlist = [example[i] for example in dataset]
        uniquValus = set(featlist)
        newEntrpy = 0.0
        for value in uniquValus:
            subData = Split_dataset(dataset, i, value)
            prob = len(subData) / float(num_sample)
            newEntrpy += prob * Cal_entrpy(subData)
        info_gain = baseEntrpy - newEntrpy
        if (info_gain > best_Infogain):
            best_Infogain = info_gain
            bestFeat = i

    return bestFeat

然后再构造一个投票及计票的函数

def Major_cnt(classlist):
    class_num = {}
    for vote in classlist:
        if vote not in class_num.keys():
            class_num[vote] = 0
        class_num[vote] += 1

    Sort_K = sorted(class_num.iteritems(), 
       key = operator.itemgetter(1), reverse=True)    
    return Sort_K[0][0]

有了这些,就可以构造我们需要的决策树了:

def Create_tree(dataset, featName):
    classlist = [example[-1] for example in dataset]
    if classlist.count(classlist[0]) == len(classlist):
        return classlist[0]

    if len(dataset[0]) == 1:
        return Major_cnt(classlist)

    bestFeat = Choose_feature(dataset)
    bestFeatName = featName[bestFeat]
    myTree = {bestFeatName: {}}
    del(featName[bestFeat])

    featValues = [example[bestFeat] for example in dataset]
    uniqueVals = set(featValues)

    for value in uniqueVals:
        subLabels = featName[:]
        myTree[bestFeatName][value] = Create_tree(Split_dataset\
              (dataset, bestFeat, value), subLabels)
    return myTree
def Get_numleafs(myTree):
    numLeafs = 0
    firstStr = myTree.keys()[0]
    secondDict = myTree[firstStr]
    for key in secondDict.keys():
        if type(secondDict[key]).__name__ == 'dict' :
            numLeafs += Get_numleafs(secondDict[key])
        else: 
            numLeafs += 1
    return numLeafs
def Get_treedepth(myTree):
    max_depth = 0
    firstStr = myTree.keys()[0]
    secondDict = myTree[firstStr]
    for key in secondDict.keys():
        if type(secondDict[key]).__name__ == 'dict' :
            this_depth = 1 + Get_treedepth(secondDict[key])
        else: 
            this_depth = 1
        if this_depth > max_depth:
            max_depth = this_depth
    return max_depth

我们也可以把决策树绘制出来:

def Plot_node(nodeTxt, centerPt, parentPt, nodeType):
    Create_plot.ax1.annotate(nodeTxt, xy=parentPt,
                            xycoords='axes fraction',
                            xytext=centerPt, textcoords='axes fraction',
                            va="center", ha="center", bbox=nodeType, arrowprops=arrow_args)

def Plot_tree(myTree, parentPt, nodeTxt):
    numLeafs = Get_numleafs(myTree)
    Get_treedepth(myTree)
    firstStr = myTree.keys()[0]
    cntrPt = (Plot_tree.xOff + (1.0 + float(numLeafs))/2.0/Plot_tree.totalW,\
              Plot_tree.yOff)
    Plot_midtext(cntrPt, parentPt, nodeTxt)
    Plot_node(firstStr, cntrPt, parentPt, decisionNode)
    secondDict = myTree[firstStr]
    Plot_tree.yOff = Plot_tree.yOff - 1.0/Plot_tree.totalD
    for key in secondDict.keys():
        if type(secondDict[key]).__name__=='dict':
            Plot_tree(secondDict[key],cntrPt,str(key))
        else:
            Plot_tree.xOff = Plot_tree.xOff + 1.0/Plot_tree.totalW
            Plot_node(secondDict[key], (Plot_tree.xOff, Plot_tree.yOff),
                     cntrPt, leafNode)
            Plot_midtext((Plot_tree.xOff, Plot_tree.yOff), cntrPt, str(key))
    Plot_tree.yOff = Plot_tree.yOff + 1.0/Plot_tree.totalD

def Create_plot (myTree):
    fig = plt.figure(1, facecolor = 'white')
    fig.clf()
    axprops = dict(xticks=[], yticks=[])
    Create_plot.ax1 = plt.subplot(111, frameon=False, **axprops)
    Plot_tree.totalW = float(Get_numleafs(myTree))
    Plot_tree.totalD = float(Get_treedepth(myTree))
    Plot_tree.xOff = -0.5/Plot_tree.totalW; Plot_tree.yOff = 1.0;
    Plot_tree(myTree, (0.5,1.0), '')
    plt.show()

def Plot_midtext(cntrPt, parentPt, txtString):
    xMid = (parentPt[0] - cntrPt[0]) / 2.0 + cntrPt[0]
    yMid = (parentPt[1] - cntrPt[1]) / 2.0 + cntrPt[1]
    Create_plot.ax1.text(xMid, yMid, txtString)
def Classify(myTree, featLabels, testVec):

    firstStr = myTree.keys()[0]
    secondDict = myTree[firstStr]
    featIndex = featLabels.index(firstStr)
    for key in secondDict.keys():
        if testVec[featIndex] == key:
            if type(secondDict[key]).__name__ == 'dict' :
                classLabel = Classify(secondDict[key],featLabels,testVec)
            else:
                classLabel = secondDict[key]
    return classLabel

最后,可以测试我们的构造的决策树分类器:

decisionNode = dict(boxstyle="sawtooth", fc="0.8")
leafNode = dict(boxstyle="round4", fc="0.8")
arrow_args = dict(arrowstyle="<-")

myData, featName = Create_data()

S_entrpy = Cal_entrpy(myData)

new_data = Split_dataset(myData, 0, 1)

best_feat = Choose_feature(myData)

myTree = Create_tree(myData, featName[:])

num_leafs = Get_numleafs(myTree)

depth = Get_treedepth(myTree)

Create_plot(myTree)

predict_label = Classify(myTree, featName, [1, 0])

print("the predict label is: ", predict_label)
print("the decision tree is: ", myTree)
print("the best feature index is: ", best_feat)
print("the new dataset: ", new_data)
print("the original dataset: ", myData)
print("the feature names are: ",  featName)
print("the entrpy is:", S_entrpy)
print("the number of leafs is: ", num_leafs)
print("the dpeth is: ", depth)
print("All is well.")

构造的决策树最后如下所示:

这里写图片描述

作者:shinian1987 发表于2017/11/13 21:52:52 原文链接
阅读:122 评论:0 查看评论

正则表达式从入门到实战

$
0
0


本文来自作者 JPM 在 GitChat 上分享「正则表达式从入门到实战」,阅读原文」查看交流实录

文末高能

编辑 | 坂本

在开发的过程中,字符串处理往往很频繁。比如我们经常会对用户输入做校验:手机号,身份证号,邮箱,密码,域名,IP 地址,URL 或者其他与字符串相关校验的业务场景。

正则表达式就是一种强大而灵活的文本处理工具,正则可以很好的解决这类字符串校验问题。掌握正则表达式,就能大大提高开发过程的效率。

正则表达式(Regular Expression)在代码中常常简写为regex。正则表达式通常被用来检索、替换那些符合某个规则的文本,它是一种强大而灵活的文本处理工具。

本场Chat将从2个方面入手:

  • 学习正则表达式的语法规则,并介绍开发过程中使用正则表达式的流程,提供一款正则工具。

  • 通过实现 5 个小功能练习使用正则,然后解决 2 个实际开发中遇到的问题。

通过本场 Chat 的学习,从零开始轻松掌握正则表达式,并且具备解决实际项目问题的能力。

一款好用的正则工具

给大家推荐一个正则工具“RegexBuddy”,( http://pan.baidu.com/s/1jHRshpW "正则工具RegexBuddy" ),密码:c509

说明:

为了便于理解,文章所有示例的正则表达式用“regex=正则”表示,“=”号后面就是正则表达式,为了阅读效果,我会把工具GegexBuddy里匹配到的字符串截图展示。

比如:regex=study regex,其中 study regex 就是一个正则,它匹配字符串“study regex”,如下:

正则表达式的语法规则

学习正则表达式语法,主要就是学习元字符以及它们在正则表达式上下文中的行为。

元字符包括:普通字符、标准字符、特殊字符、限定字符(又叫量词)、定位字符(也叫边界字符)。下面分别介绍不同元字符的用法。

普通字符

字母[a-zA-Z]、数字[0-9]、下划线[-]、汉字,标点符号:

  • 匹配字母a可以 regex=a

  • 匹配字母b可以 regex=b

  • 匹配字母a或者b可以 regex=a|b,这个正则引入一个特殊字符“|”,专业名称为“或”,你也可以叫它“竖线”,它表示“或”的意思。

  • 匹配字母a或者b或者c可以 regex=a|b|c

  • 匹配字母a或者b或者c或者d可以 regex=a|b|c|d

  • 如果匹配所有26个字母,这种写法明显很二了。

这里引入两个特殊字符方括号“[ ]”和中划线“-” “[ ]”,专业名称为“字符集合”,你也可以叫它“方括号”。

“-” ,表示“范围”,你也可以叫它“到”,regex=[A-Z] 匹配从A到Z26个字母中的任意一个。

那么匹配字母a或者b或者c或者d可以 regex=[abcd]。

匹配数字1到8的任意数字可以 regex=[1-8],这样就不会匹配到0与9这2个数字了,如下:

标准字符集合

标准字符集合是能够与“多种普通字符”匹配的简单表达式,比如:\d、\w、\s。

匹配数字0到9的任意数字可以 regex=[0-9] 也可以 regex=\d。标准字符集要注意区分大小写,大写是相反的意思。

regex=\D,则匹配非数字字符,即不能匹配数字0到9,如下:

常用的标准字符说明 标黄的要熟记。

特殊字符

特殊字符在正则表达式中表示特殊的含义,比如:*,+,?,\,等等。

  • “\”是转义字符,用于匹配特殊字符

  • 匹配反斜杠“\”可以 regex=\\,因为“\”是特殊字符,所以需要在它前边再加一个“\”进行转义

  • 匹配星号“*”,可以 regex=\,因为“\”是特殊字符,所以需要在它前边再加一个“\”进行转义

常用的特殊字符说明 标黄的要熟记。

限定字符

限定字符又叫量词,是用于表示匹配的字符数量的。

  • 匹配任意1位数字可以 regex=\d

  • 匹配任意2位数字可以 regex=\d\d

  • 匹配任意3位数字可以 regex=\d\d\d

匹配任意8位数字,再这么写就有点二了。这里引入用于表示数量限定字符“{n}”。“{n}”,n是一个非负整数,匹配确定的n次。

注意:regex=\d\d{3} 匹配任意4个数字不是6个,量词只对它前面的字符负责, regex=\d\d{3} 匹配的内容如下:

  • 匹配任意8位数字可以 regex=\d{8}

  • 匹配任意8位以上的数字可以 regex=\d{8,}

  • 匹配任意1到8位以上的数字可以 regex=\d{1,8}

从上图,我们可以看到 regex=\d{1,8},可以匹配到任意1-8个数字,超过8位数字后,从新开始匹配。

匹配次数中的“贪婪模式”与“非贪婪模式”:

正则的匹配默认是贪婪模式,即匹配的字符越多越好,而非贪婪模式是匹配的字符越少越好,在修饰匹配字数的量词后再加上一个问号“?”即可。

那么同样是上面的字符串,regex=\d{1,8}?匹配到什么呢?

因为在{1,8}这个量词后面加上了问号“?”,表示非贪婪模式,所以只能匹配到1个数字,即匹配的字符越少越好。

常用的限定字符说明 标黄的要熟记。

  • 匹配0个或多个字母A可以 regex=A* 或者 regex=A{0,}

  • 匹配至少一个字母A可以 regex=A+ 或者 regex=A{1,}

  • 匹配0个或1字母A可以 regex=A?或者 regex=A{0,1}

匹配至少一个 Hello可以 regex=(Hello)+,匹配的效果如下:

定位字符

定位字符也叫字符边界,标记匹配的不是字符而是符合某种条件的位置,所以定位字符是“零宽的”。

常用的定位字符:

匹配以 Hello 开头的字符串可以 regex=^Hello

匹配以 Hello 结尾的字符串可以 regex=Hello$,如下:

匹配以H开头以o结尾的任意长度字符串可以regex=^H.*o$,如下:

\b匹配这样一个位置:前面的字符和后面的字符不全是\w。如果在“hello,hello1 helloregex,hello regexhello.”这个字符串里匹配regex=hello\b,匹配到的结果如下:

分析一下:为什么 hello1 匹配不了“hello\b”这个正则?

首先\b是一个定位字符,它是零宽的,标识一个位置,这个位置的前面和这个位置的后面不能全是\w,即不能全是字母数字和下划线 [A-Za-z0-9_],而hello1的o与1之间的位置前面是o后面是1,前后全是\w,不符合\b匹配的含义,因此hello1不能匹配正则表达式 “hello\b”。

但是 bhello 可以匹配 “hello\b” 这个正则,因为 hello 的结尾的位置,前面是o,后面是空白,所以符合\b匹配的含义,因此 bhello 可以匹配 “hello\b” 这个正则。

自定义字符集合

方括号[ ]表示字符集合,即[ ]表示自定义集合,用[ ]可以匹配方括号里的任意一个字符。

regex=[aeiou] 匹配“a”,“e”,“i”,“o”,“u”任意一个字符,也就是可以匹配集合 [aeiou] 的任意一个字符。

但是,特殊字符(除了小尖角“^”和中划线“-”外)被包含到方括号中,就会失去特殊意义,只代表其字符本身。

regex=[abc+?] 匹配“a”,“b”,“c”任意一个字符或者**“+”,“\”,“?”,即包含在自定义集合中的特殊字符“+”,“*”,“?”**失去了特殊含义,只表示其字符本身的意思。

特殊字符小尖角“^”,原本含义是匹配字符串的开始位置,如果包含在自定义集合[ ]中,则表示取反的意思。

比如:regex=[^aeiou] 匹配“a”,“e”,“i”,“o”,“u”之外的任意一个字符。

中划线“-”,在自定义集合[ ]中,表示“范围”,而不是字符“-”本身,regex=[a-z],匹配从a到z中26个字母中的任意一个。

除小数点“.”外,标准字符集合包含在方括号中,仍然表示集合范围。regex=[\d.+] 匹配0-9的任意一个数字或者小数点“.”或者加号“+”

也就是说\d在自定义集合中仍然表示数字,但是小数点在字符集合中只表示小数点本身,而不是除“\r\n”之外的任何单个字符。

选择符和分组

regex=x|y,匹配字符x或y。( )表示捕获组,( )的作用如下:

  1. 括号中的表达式可以作为整体被修饰,用来表示匹配括号中表达式的次数,regex=(abc){2,3},可以匹配连续的2个或3个abc,如下:

  2. 括号中的表达式匹配到的内容会存储起来,并可以获取到括号中表达式匹配到的内容

  3. 每一对括号会分配一个编号,使用( )的捕获根据左括号的顺序从1开始自动编号,编号为0的捕获是整个正则表达式匹配到的文本。

捕获组( )可以把匹配的内容存储起来,那么如何获取( )捕获到的内容呢,下面介绍反向引用。

反向引用 “\number”

每一对括号会分配一个编号,使用( )的捕获根据左括号的顺序从1开始自动编号。

通过反向引用,可以对分组已捕获的字符串进行引用。“\number” 中的 number 就是组号

regex=(abc)d\1 可以匹配字符串 abcdabc,即\1表示把获取到的第一组再匹配一次,如下:

(?:pattern) 表示非捕获组,匹配括号中表达式匹配到的内容,但是不进行存储匹配到的内容。

这在使用 “或” 字符?(|)?来组合一个正则的各个部分是很有用的。

例如:匹配字符 “story” 或者 “stories”,regex=stor(?:y|ies) 就是一个比 regex=story|stories 更简略的表达式。

预搜索

预搜索,又叫零宽断言,又叫环视,它是对位置的匹配,与定位字符(边界字符)类似。

regex=love (?=story)匹配的结果如下(匹配 “love?” 后面是 story):

regex=love (?!story)匹配的结果如下(匹配 “love” 后面不能是 story):

运算符的优先级

正则表达式从左到右进行计算,并遵循优先级顺序,这与算术表达式非常类似。下表的优先级从高到低排序。

说明:“|” 或操作是优先级最低的,它比普通字符的优先级低。因此,regex=r|loom 匹配 “r” 或 “loom”,如下:

如果想匹配 “room” 或 “loom”,请用括号创建子表达式,regex=(r|l)oom,如下:

开发过程中使用正则表达式的流程

  1. 分析所要匹配的数据特点,模拟各种测试数据;

  2. 利用正则工具,写正则表达式与测试数据进行匹配,从而验证你写的正则;

  3. 在程序里调用在正则工具中验证通过的正则表达式。

练习使用正则实现 5 个小功能

电话号码的正则

  1. 电话号码由数字和“-”组成

  2. 如果包含区号,那么区号为三位或四位,首位是0

  3. 区号用“-”和其他数字分割

  4. 除了区号,电话号码为7到8位

  5. 手机号码为11位

  6. 11位手机号码的前2位为“13”,“14”,“15”,“17”,“18”

分析

电话号码分为固话和手机号,首先匹配固话,然后匹配手机号。

  • 固话的正则:regex=0\d{2,3}-\d{7,8}

  • 手机号的正则:regex=1[34578]\d{9}

所以电话号码的正则:

regex=(0\d{2,3}-\d{7,8})|(1[34578]\d{9})

电话号码匹配结果如下:

身份证号码的正则

  1. 长度:15位或者18位

  2. 如果是15位,则都是数字

  3. 如果是18位,最后一位可能为数字或字母X

分析

  • 15位数字:regex=\d{15}

  • 18位数字:regex=\d{18}

  • 17位数字+X:regex=\d{17}X|x

所以省份证号码的正则:

regex=(^\d{15}$)|(^\d{18}$)|(^\d{17}X|x$)

身份证号码匹配的结果如下:

电子邮箱的正则

  1. 邮箱格式:用户名@网址.域名

  2. 用户名:字母、数字、下划线组成

  3. 网址:字母、数字

  4. 域名:2-4位字母组成,1-2个域名

  5. 不区分大小写

分析

  • 用户名:regex=\w+。

  • 网址:regex=[a-zA-Z0-9]+。

所以电子邮箱的正则 regex=\w+@[a-zA-Z0-9]+(.[a-zA-Z]{2,4}){1,2}。

电子邮箱匹配的结果如下:

IP地址的正则

IP地址的格式:(1~255).(0~255).(0~255).(0~255)

分析

  • 1-255的正则 regex=^([1-9]|[1-9]\d|1\d\d|2[0-5][0-5])。

  • 0-255的正则 regex=^(\d|[1-9]\d|1\d\d|2[0-5][0-5]) 。

所以 IP 地址的正则 regex=^([1-9]|[1-9]\d|1\d\d|2[0-5][0-5]).((\d|[1-9]\d|1\d\d|2[0-5][0-5]).){2}(\d|[1-9]\d|1\d\d|2[0-5][0-5])$

IP地址匹配的结果如下:

日期格式的正则

日期格式:yyyy-mm-dd

分析

  • 4位的年,第一位只能是1或2,regex=^([12]\d{3})

  • 一年的12个月(01~09和1~12),regex=^(0?[1-9]|1[0-2])

  • 一个月的31天(01~09和1~31), regex=^((0?[1-9])|((1|2)[0-9])|30|31)

所以 格式为 yyyy-mm-dd 的日期正则 regex=^([12]\d{3})-(0?[1-9]|1[0-2])-(0?[1-9]|((1|2)[0-9])|30|31)$

yyyy-mm-dd 的日期匹配的结果如下:

在 Java 代码中如何使用正则

Java 中正则相关类位于 java.util.regex 包下,主要使用2个类,如下:

Pattern 类:

  • Pattern 是正则表达式 regex 的编译表示形式

  • 代码:Pattern pattern = Pattern.compile(regex);

Matcher 类:

  • 通过解释 Pattern 对输入的字符串 input 执行匹配操作的引擎

  • 代码:Matcher matcher = pattern.matcher(input);

注意:在 Java 代码中转义字符“\”要写成“\”才表示一个“\”。比如regex=\d,在 Java 代码中应该写成“\d”。

Java示例代码:  

package regex; import java.util.regex.Matcher; import java.util.regex.Pattern; public class TestRegex {    public static void main(String[] args) {        String input = "Hello regex 666!";        // java中要想表示\需要通过转义字符\进行转义        String regex = "\\w+";        Pattern pattern = Pattern.compile(regex);        Matcher matcher = pattern.matcher(input);        // matches()方法,将输入的整个字符串与给定的正则匹配        System.out.println(matcher.matches());        // 结果为:false,因为"Hello regex 666!"不全是\w        String regex1 = "\\d+";        Pattern pattern1 = Pattern.compile(regex1);        Matcher matcher1 = pattern1.matcher(input);        // find()方法,从输入的字符串里找出与给定的正则匹配的子串        while (matcher1.find()) {            // 只要找到,则就能通过group()方法获取到符合条件的子串            System.out.println(matcher1.group());            // 结果为:666,通过find()找到了\d,通过group()方法获取匹配到的值        }    } }

利用正则解决 2 个实际开发中遇到的问题

问题1:一键获取短信验证码

短信验证码在目前的互联网应用的非常广泛,在一些重要操作中都需要输入短信验证码来验证身份信息。

列举3条不同的验证码短信内容如下:

  1. 【京东】尊敬的用户,634561是您本次的省份验证码,30分钟内有效,请完成验证。

  2. 【滴滴】您的验证码是6678,请在页面中提交验证码完成验证。

  3. 【百度】376687(动态验证码),请在30分钟内填写。

那么如何通过一个正则表达式来获取到3个不同类型的短信内容里的数字验证码呢?

首先分析以上3条短信内容,找出共同点:

  1. 验证码都是数字,可以是4位数字,也可以是6位数字

  2. 每条短信都包含“验证码”3个汉字

  3. “验证码”3个字与数字的顺序关系,“验证码”3个字可以在数字前,也可以在数字后

按照以上的分析,我们就可以写在正则工具里写正则表达式进行验证了。

  1. 4位数字或者6位数字,可以用 “\d{4}|\d{6}” 来匹配,我们使用捕获组( )来获取数字部分,即 regex=(\d{4}|\d{6})

  2. 验证码3个字就用“验证码”来匹配,regex=验证码

  3. “验证码”3个字在数字前,可以 regex=验证码 \D(\d{4}|\d{6}),“验证码”3个字在数字后,可以 regex=(\d{4}|\d{6})\D 验证码,这2个表达式是或的关系,需要用到括号来组织这2个表达式,然后再用或“|”来进行选择,即 regex=(验证码\D(\d{4}|\d{6}))|((\d{4}|\d{6})\D验证码)

  4. 由于要通过捕获组( )来获取数字内容,又要用括号来组织关系,因此需要把或 “|” 两边的表达式部分用非捕获组 (?:) 来标记,因为我们只需要获取数字部分的括号( )匹配到的数字。

    即 regex=(?:验证码\D(\d{4}|\d{6}))|(?:(\d{4}|\d{6})\D验证码)

最后我们把分析到的表达式代入到 Java 代码完成功能。注意在 Java 中,反斜杠需要转义,即一杠变二杠。

Java代码:

package regex; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class TestRegex {    public static void main(String[] args) {        List<String> inputList = new ArrayList<String>(16);        inputList.add("【京东】尊敬的用户,634561是您本次的省份验证码,30分钟内有效,请完成验证。");        inputList.add("滴滴】您的验证码是6678,请在页面中提交验证码完成验证。");        inputList.add("【百度】376687(动态验证码),请在30分钟内填写。");        String regex = "(?:验证码\\D*(\\d{4}|\\d{6}))|(?:(\\d{4}|\\d{6})\\D*验证码)";        Pattern pattern = Pattern.compile(regex);        System.out.println("一键获取到的验证码如下:");        for (String input : inputList) {            Matcher matcher = pattern.matcher(input);            if (matcher.find()) {                for (int i = 1; i <= matcher.groupCount(); i++) {                    if (matcher.group(i) != null) {                        System.out.println(matcher.group(i));                    }                }            }        }    } }

运行效果:

问题2:判断用户密码是否为强密码

用户设置的密码弱,会导致信息安全问题,一般的系统都要求设置强密码。下面是京东注册页面的截图:

以京东注册为例,京东建议使用字母、数字和符号两种及以上的组合,6-20个字符。

下面我们通过正则表达式来完成用户输入的密码是否符合密码规则的校验。首先分析密码要求,如下:

  1. 密码包括字母、数字和符号3种字符

  2. 必须包含2种及以上的字符

  3. 密码长度6-20位

  4. 字母包括:A-Za-z,数字包括:0-9,

  5. 符号包括32个:`-=][‘;/.,~!@#$%^&()_+|}{“:?><

需要注意的是如果使用32个符号,特殊字符“\”、“[”、“]”是需要进行转义的,为了简单直观,我们假设符号只有@#$3个。

进一步分析,密码只有字母,数字,符号3种类型的字符,要求必须包含2种及以上,那么密码组合的种类有4种(3个里面选2个+3个全选=4),即:字母+数字,字母+符号,数字+符号,字母+数字+符号。

如果从正面去考虑这个问题,那么正则会很难写,所有我们从反向考虑:“必须包含2种及以上”的反向就是“只包含1种”,也就是说密码要求“不能只包含1种字符”。

密码长度6-20位,需要用到开始标记“^”和结束标记“$”,量词{6,20}

最终分析密码要求是:密码从开始到结束必须 6-20 位而且不能全部是1种单一的字符

因此正则可以这么写:

解释:

  • ^(?![A-Za-z]+$)表示从头到位不能全是字母

  • ^(?![0-9]+$)表示从头到位不能全是数字

  • ^(?![@#$]+$)表示从头到位不能全是符号@#$

  • ^[A-Za-z0-9@#$]{6,20}$表示从头到位只能是字母数字符号@#$的集合

需要注意的是,开始符“^”和预搜索“(?!)”都是零宽的,表示位置,所以开始符“^”只需要在整个正则表达式的开始处写一个即可。

最后我们把分析到的表达式代入到 Java 代码完成功能。

Java 代码片段:

密码校验效果:

近期热文

小白入坑 Web 渗透测试必备指南

前端攻城狮该了解的 Vue.2x 响应式原理

前端工程师“应试”指南

如何用 Node.js 爬虫?

两款敏捷工具,治好你碎片化交付硬伤

作者:GitChat 发表于2017/11/14 0:00:00 原文链接
阅读:0 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>