自我介绍

前端 - > 后端 -> 数据挖掘机

ML DM AI 的区别

我的自学之旅

给新手的推荐

  • 机器学习课程(MOOC)
  • Kaggle、天池、数据城堡

掌握的技能

  • Java + Python
  • 数据可视化
  • 训练团队感

未来的发展方向

  • 全栈数据挖掘工程师
  • 增长黑客
  • ML算法工程师

由于最近在一家数据服务公司实习,项目需要了解分布式,所以在这里基于scrapy的分布式总结一下爬虫的分布式实习

分布式起因

单机无法完成全部工作任务所以要使用集群加速完成工作任务

分布式有点像蚁群,一只蚂蚁举不起一只卡壳虫,但是几百只就能轻松的把他运回家

但是分布式设计必须科学,否则就像下面一样,一个和尚挑水,其他和尚围观

分工不合理,来源网络

分布式设计

分布式设计原理在于分工

首先我们来看看爬虫怎么进行分工,单个爬虫运行根据url获取响应报文,然后通过解析报文返回结果或者下一次爬取目标,如果单个爬虫我们只要在内存维持一个set变量记住爬取过的url,这就是scrapy默认的方法。

但是我们无数个爬虫由于不在同一个进程,无法共享变量,所以我们只要让一个“variable(变量)”能够被被所以爬虫共享到就完成了主要功能

现在我们来完善具体细节
要求:

  • 爬虫能够轻松读取所以已爬取变量
  • 爬虫能够加入已读取变量
  • 爬虫能够获取下一次请求具体参数

原则上我们可以使用内存映射来构建这个变量,但是读取,修改都不便利,所以可以先使用redis作为存贮变量的地方,使用redis提供的set我们替代scrapy框架的set变量。

现在我们已经决定我们要使用什么容器来存贮变量,接下来我们要考虑存什么变量。

我们先看scrapy-redis存贮了什么,分析源代码可知,scrapy-redis将返回的Requestpickle话存入数据库,并且计算这个Request的32位hash值存入redisset中过滤列表。

scrapy-redis通过修改scrapy的调度器(scheduler)让其当爬虫没有Request需要处理时在redis中提取Request,实现分布式。

我们来分析一下这种方法,爬虫在爬取的过程中从master端获取Request,并不断生成Requestmaster端,master只是一个redis数据库,负责对url去重,分发任务。

我们来比较一下直接存取url这种方法,这种方法好处在于,slaver能够从上一个Request中获取全部信息,假如上一个Request需要存取获取的表单提取地址,我们下一次爬虫发起Request就能从上一个Request中获取参数。

当然由于我们存贮的是Request,一个Request pickle化之后的字符串比较长,当我们的任务列表里面有很多Request的时候,redis占用的内存会非常巨大。

当然如果爬虫启动的够多,生成一个就能把任务被调度下去,那么这个任务列表就能稳定在一个可控的范围。

总结

每个爬虫即负责爬取数据,又负责生成下一个任务,即无主次之分,我们可以一次性在docker中启动上百个实例,我们只是用redis充当一个存放变量的地方。

但是这种方法也有一个缺点,我们不能自由的添加初始url,要想添加新的爬取任务,必须新建一个爬虫更新初始url,我们如果是想搭建一个自由添加url的爬虫,这种实现方式不大优雅。

分布式改良

我们要修改程序框架,达到随时可以添加要爬取新任务,然而不影响爬虫集群

爬虫框架

我们独立出来mastermaster负责生成Request去重以及任务调度,而slaver只负责从master获取任务爬取。

这种方法我们可以很轻松对master改良而不影响slaver,通过让master定时从数据库中获取新的任务生成到任务列表,我们可以轻松添加新的任务到slaver集群中去。

下一步我们就介绍如何修改scrapy-redis达到我们新框架需要

重构scrapy-redis

参考:
基于Redis的三种分布式爬虫策略

引言

递归是高度抽象化问题的一个好东西,我们能从很多算法书里面看到这个,
但是递归虽然对于人来说好理解,但是计算机执行起来会损失性能,一个差的递归可能会耗光计算机的资源

接下来我们来看一个非常经典的算法问题Fibonacci数

f(n) = n (n < 2)
f(n) = f(n-1) + f(n-2)   (n >= 2)

我们可以很轻松的用递归解决掉它

def fibonacci(n):
  if n < 2:
      return n
  else:
      return fibonacci(n-1) + fibonacci(n-2)

n比较小的时候很快就出结果了,但是当n大于100时候要很久才能出结果,如果n大于1000,直接报出超出迭代深度的错误(python默认迭代深度是1000)

现在我们来解决两个问题

  1. 为什么n大于100时候就很久才能算出结果
  2. 为什么n大于1000就报迭代深度的错误

首先我们要知道一个概念就是堆栈段,每个进程开始运行时都会初始化一个堆栈段,这在物理上就是一小块内存,初始化堆栈段的时候计算机要做一些看起来同程序毫无关系的事情,比如说将寄存器的值推入堆栈里面等等

当你在运行主程序的时候你调用一个子函数,系统又会在当前堆栈段新建一个堆栈段,你子程序运行完了后会删掉这个堆栈段回到主程序,但是递归有个问题,就是他调用子程序的时候不会立即返回又会再调用自己

没办法因为子程序还没返回,所以计算机又初始化一个堆栈段,一个n为10的fibonacci函数就会初始化掉 2 ** 10 = 1024个堆栈段,n越大值会指数型增长,虽然1000个初始化在当今计算机上发不了多少时间,但是当我们n大于20就要 百万次初始化了

这就是为什么n很大的时候要很久才能算出结果,在一些单片机上面,循环调用空函数就是延时的功能,原理也就是堆栈初始化耗时间,而且不但耗时间假如像递归这样调用上百万次初始化而不返回将会耗掉大量内存在堆栈段上。

对策

要解决这两个问题,一种方法是改算法,使用非递归算法,这个网上有很多,感兴趣的可以去搜一下,第二种是使用协程解决递归问题

如何使用协程来解决递归呢我们先改主程序,将return换成yield

def _fibonacci(n):
  if n < 2:
      yield n
  else:
      yield ( (yield (_fibonacci(n-1)) + (yield (_fibonacci(n-2)))

接下啦我们运行一下函数

>>> _fibonacci(10)
 <generator object _fibonacci at 0x00000013A74779E8>

没有返回结果,返回一个生成器,那我们用list简单的试一下吧

>>> list(_)
......
......
TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'

生成器小知识

这里补充几点生成器的知识,懂得可以跳过

生成器大家都用过,无论是Python2Python3都不陌生,最简单的生成器是这种

>>> items = ( x for x in range(10))

我们一般搭配for来使用

>>> for i in items:
...         print(i)
...

我们也可以用协程来实现这个生成器

def iter_func(n):
    for i in range(n):
        yield n

像上面一样使用for就能实现一样的功能,在这个例子里面yield好像变成了一个return的作用,在for语句中,随着每次请求都会return一个数过来

在这个里面yield好像就是这么个功能,但是yield的作用远远不止于此

我们现在来改一下这个函数

def iter_func(n):
    for i in range(n):
        r = yield i
        print('result', r)

我们用list来运行一下这个函数

>>> list(iter_fun(2))
0
result None
1
result None

r返回了一个None,我们尝试自己实现一下for循环,有两种方式

  • next(generator)
  • generator.send(msg)

先尝试用next

>>> it = iter_fun(2)
>>> next(it)
0
>>> next(it)
result None
1

我们介绍一下next函数, next接受两个参数,第一个是生成器,第二个是返回的默认值,next函数在这里相当于下面这个函数

def next(iterator, default=None):
    try:
        iterator.send(None)
    except StopIteration:
        if default:
            return default
        else:
            raise StopIteration()

为什么第二个执行了print函数而第一个没有执行?

生成器工作原理

这里我们介绍一下生成器的工作原理

当我们使用调用一个函数的时候,一般是碰到return或者执行全部函数就会返回父函数

但是生成器不同,假如他执行函数碰到yield,他就会直接返回一个生成器。

这个生成器我们可以把它看做是邮递员,我们必须写好目的地,他才会帮我们把信寄出去。

现在我们分析一下生成器的具体流程,我们先定义一个简单的生成器

def mygenerator(n):
    while True:
        r = yield n
        n -= 1
        print('result', r)

然后我们调用这个生成器

>>> i = mygenerator(10)
>>> i
<generator object mygenerator at 0x7f420a339d00>

我们得到一个生成器,我们先尝试发送一个地址给“邮递员”

>>> i.send(0)
...
TypeError: can't send non-None value to a just-started generator

我们得到一个错误,必须传递一个None,我们先不管,先送一个None值过去

>>> i.send(None)
10

我们得到一个10,再送一个地址过去

>>> i.send(None)
result None
9

我们现在来分析一下代码,第一次调用的时候直接返回了,第二次调用我们从r = yield n那行开始执行,并且运行到第二个r = yield n那里停止了

就可以解释上面为什么要第一次传递None过去,因为第一次调用它会直接返回yield后面的值给我们,第二次调用 我们可以根据第一次生成器递给我们的值,决定我们第二次想寄的“信”,因为第一次传递过去“信”并不能被处理,所以Python强制我们传递一个None值过去


我们回到上面的函数

def _fibonacci(n):
    if n < 2:
        yield n
    else:
        yield ( (yield (_fibonacci(n-1)) + (yield (_fibonacci(n-2)))

我们来分析一下流程,为了解决上面的问题我们先把函数简化,去掉递归

def f(n):
    yield (yield n) + (yield n - 1)

我们先创建一个生成器i

>>> i = f(5)
>>> i
 <generator object f at 0x7f4a421d8f10>

我们先启动i

>>> i.send(None)
5

我们再把得到5传给i

>>> i.send(5)
4

我们得到yield n -1返回的4,我们再把4传给i,得到最终结果

>>> i.send(5)
9

假如我们把后面两个send的值换成其他值我们会得到不同的结果,这里我们可以看到我们,要实现上面函数必须要依靠一个,保存我们返回的生成器,然后依次调用生成器返回结果,具体代码如下

def fibonacci(n):
    stack = [ _fibonacci(n)]
    last_result = None
    while stack:
        last = stack[-1]
        try:
            if isinstance(last, types.GeneratorType):
                stack.append(last.send(last_result))
                last_result = None
            else:
                last_result = stack.pop()
         except StopIteration:
             stack.pop()
     return result

我们这里用stack作为我们的堆栈,用last_result保存上一个生成器返回的值

总结

我们使用协程解决掉了递归错误,但是这个方法并不可以给我们算法加速,虽然n为1000以上不会报递归错误,但是等待的时间还是很长很长。。。

虽然协程在这个方法里面并没有起到多大作业,协程在算法方面还是没有太多帮助,协程在计算机I/O还有网络请求方面有更好的效率,但是这次尝试让我们对协程如何使用有了一个清晰的了解

有兴趣的可以去了解一下协程在异步网络请求的应用

第一次听说这个比赛还是去年在知乎上,当时也不知道这个比赛具体是怎么回事,当时自己还是一个小白,忙着搞懂各种主流的机器学习模型算法。

当时在我心中,模型算法是数据挖掘的最重要的组成部分,搞懂这些才能真正搞定数据挖掘。我当时对算法模型和数据的理解是:模型就是风车,数据就是流水。我要做的事就是撘一个强健的风车,让数据流过。

当我还没接触实际的工作前,我还没有没有从编程工转向挖掘工。我太注重编程本身了,而忘记我自己真正要挖掘的宝藏。

我以前在Quora上搜如何成为数据科学家,我发现很多有经验的数据科学家他们都把“对数据的敏感和兴趣”作为数据科学家最重要的特征,而“了解各种算法模型并能应用到数据上”才是第二重要的。我当时不是太理解,我觉得后者才是更重要的。

参赛感想

这次参赛算是我学习数据挖掘第一次实际的挖掘,以前学习各种算法模型都是准备的很好的数据,只要套上算法模型就能跑的很好。所以我一开始就拼命的去找类似的大赛,看看获胜者他们用的模型是什么。

这几天我好像抱着一堆瓶子,拼命的想把巨大的石头(数据)塞进瓶口里,看起来工作量很大,流了很多汗,其实什么都没有干。今天在看一个类似的比赛选手答辩的时候的视频,突然明白自己好像走了一个死胡同。自己拼命的想这找一个合适的瓶子(模型),其实我更应该做的是把石头(数据)磨碎。

模型本身不重要,他只是一个载体,更重要的是数据。

第一次参加这样大型比赛,有点激动也有点惶恐,如何将所学的应用到实际,还有在实际中提高自己还有待自己“挖掘”。虽然这个比赛奖金“丰富”,但是我觉得在这个比赛中得到的体会乐趣比奖金更诱人。


比赛还有一个月,在这里立个小目标,争取跑到到前五页,我也会尽量抽时间把自己感想写出来。
未完待续。

这是我从Quora上看到的一篇非常简短但详细的数据科学家的‘技能点’
来自eBay的一个数据科学家的回答
翻译来自Quora回答

这是面试谷歌、英特尔、脸书等大的世界五百强公司的数据科学家相关岗位常见的技术要求,在我看来主要有七点

  • 基本的编程基础

你应该了解一门统计学相关的编程语言,比如说RPython(同时要了解NumpyPandas库),还要一门数据库查询语言比如SQL

  • 统计学

你应该要能解释零假设、P值、最大似然估计和置信空间这些短语,统计学在非常巨大的数据库里压缩数据和从挑选最重要的特征非常重要,在你得出结论和设计实验过程中也帮助巨大

  • 机器学习

你必须能够搞懂K-近邻、随机森林和集合方法等机器学习算法,这些算法基本上都在RPython中得到实现,这些算法能告诉你雇主你能够将计算机科学运用在实际的管理中。

  • 数据重组

你应该要能够“清理”数据。比如数据库中”California” (加利福利亚)和“CA”是一样的,数据库里面可能出现用负值代表人口。这个总的来说就是识别坏(或者不正确)的数据然后校正(或删除)他们。

  • 数据可视化

数据科学家不能就只是自己搞懂就行,他们需要把他们发现告诉你的产品经理,这样就能确保数据能很好的应用到程序里面去。所以,熟悉数据可视化工具比如说ggplot非常重要(这样你就能展示你的数据而不是仅仅谈谈而已)

  • 软件工程

你应该了解算法和数据结构,因为这些东西在你写高效率的机器学习算法时非常重要,知道如何使用分支和使用高效的数据结构:队列、数组、列表、堆栈、树等等。

  • 产品管理

这个绝对是有争议的,但是那些了解产品的人将会知道什么指标是最重要的。这里有很多数据可以用来做A/B测试,但是产品导向的数据科学家将会把最好的指标用来做测试。你要知道这些的意思:可用性测试、线框、保留和转换率、流量分析、客户反馈、内部日志、A/B测试。

前言

因为手头自己有三个服务器,所以想折腾一下负载均衡。

两个CentOS,一个Ubuntu,都是比较新的。

一开始准备用haproxy来做负载均衡服务器,因为haproxy相比与nginxcookiesession支持比较好,但是由于两个原因还是放弃了。

  1. 服务器被阿里云封掉

简单的在haproxy中设置后端服务器后,过一段时间就显示强制备案页面,由于我的域名没有备案。

后来我翻看了nginx日志发现,haproxy默认在request header里面带了X-Host,被阿里云发现了,这里提供一个解决方法

# 删除掉你header里面的 Host
# 在backend里面添加一句
http-request del-header Host

然而nginx里面默认是没有添加Host这个的,要你在localtion中添加两句,如下面

    server {
  listen 80;
  server_name example.com;
  location / {
    proxy_pass       http://main;
    proxy_set_header Host            www.example.com; # add Host
        proxy_set_header X-Forwarded-For $remote_addr; # add X-Forwarded
  }
}
  1. haproxy支持多开

我试了很多种选项,确定pidfile、改变uidgid等等,haproxy似乎可以允许很多个相同进程绑定同一个端口,虽然可以通过pid来写一套类似service管理的脚本,但终归很麻烦

我看网上有人写了这个脚本,但是nginx自带了,还是用nginx比较好,而且ansibleservice的交互还不错。

nginx负载均衡

nginx负载均衡是通过反向代理来实现的,也就是把一台服务器的压力分摊到多台上面

要想实现这个必须要有后端服务器,假设我们有一台后端服务器1.1.1.1,在代理主机的nginx配置系统location里面只要添加一条proxy_pass就行了

    server {
listen 80;
server_name example.com;
location / {
    proxy_pass http://1.1.1.1;
}
}

上面只是简单的实现了一个反向代理的功能,当你有一个后端服务群的时候,你就要使用负载均衡模块了,负载均衡模块在nginx配置特别简单,添加一个upstream模块,把服务器ip或者域名放到里面

  upstream webservers{
    server 1.1.1.1 weight=10;
    server my.domain.com  weight=10;

}

然后修改proxy_pass后的为http://webservers就行了

ps: nginx对于后端反向代理服务器有个max_failsfail_timeout属性,你要是设定了一个max_fails次数,你代理服务器拿取失败了几次就会在fail_timeout值之后尝试,和haproxyretry属性差不多,但是似乎haproxyretry不好使,我故意使用两个错误ip和正确ip,结果nginx能一直正确返回正确ip响应,而haproxy有时候能,有时候不行。

nginx错误日志

在调试nginx碰到一些错误,记录一下如何系统的解决方法

  • 调用service nginx start失败

首先看给的错误信息,假如让你看systemctl status nginx.servicejournalctl -xn,输入去看

  1. 格式错误(format error)

一般你写的nginx的配置文件有问题,这时候可以用nginx -t检查格式,修改正确后会显示success

  1. 无法绑定地址(bind error)

一般是因为有别的应用程序占用端口造成的,这时候用netstat -tulpn检查端口,然后选择kill掉占用端口的程序或者换一个端口

ansible playbook 编写

具体代码可以参考nginx均衡负载ansible-playbook
首先你得写一个hosts

[ali]
my ansible_ssh_host=1.1.1.1 ansible_ssh_user=root 
[tencent]
main ansible_ssh_host=1.1.1.2 ansible_ssh_user=root
[digital]
google ansible_ssh_host=1.1.1.3 ansible_ssh_user=root

前面[ ]包着的是组名,最前面的mymaingoogle别名,后面就是ip和用户名了。

写完hosts后要写两个nginx配置文件一个代理服务器的配置文件和一个后端服务器配置文件,playbook很简单就是复制nginx配置文件和重启nginx

---
- hosts: tencent
  remote_user: root

    tasks:
  - name: copy nginx config file 
    template: src=~/test/lunge_proxy.conf  dest=/etc/nginx/conf.d/lungelog.conf
    notify: restart nginx

    handlers:
  - name: restart nginx
    service: name=nginx state=restarted enabled=yes

解释一下notify,在复制完成之后就启用一个handler完成nginx的重启,当然这里也可以使用reload,假如在生产环境的话。

客户端和代理的playbook差不多就不多介绍了。

引用

nginx的配置、虚拟主机、负载均衡和反向代理

驱动力是什么

驱动力就鞭子,小的时候我们被父母教育考的好就是棒棒糖,考的差就是鞭子,等我们走进社会,工资就是我们是驱动力。

我们看到过那些年薪百万的程序员,也看到过一些碌碌无为的码农,每个人都想成为那群大牛,工作得心应手、万人敬仰,工资难以”望其项背”.

但是我们同大牛和码农(差点打成马蓉…..)有什么区别呢,有些人说是人家那些大牛早早就积累了十万个小时,我们同大牛只是差了十万个小时.

这从某一方面上来看是对的,从某一方面来说又是不对的,君不看那些在公司辛辛苦苦工作几十年的码农早就积累了几十万小时,但是他们依旧是码农,除了业务逻辑比新手强。

那是什么原因让几十万个小时造不了一个大神呢?

很简单,就是驱动力

码农是以工资作为驱动力的,而大神是以兴趣为驱动力的,很多码农一开始都同大神一样被编程的乐趣而吸引,然而大神坚持下来了,而码农呢,慢慢的像小孩子玩厌了新玩具,在慢慢的走入社会被工资左右,在慢慢的就开始盼望早点下班……

怎么改变

环境对我们的影响是潜移默化的,我们处在这个环境里面可能不知不觉就慢慢改变我们自己了

那我们如何改变自己

工作环境

假如你是一个工作党,找一个好的开发团队对你的影响是巨大的,假如你的小组死气沉沉,最好换一个即使工资很低

假如你是一个学生党,比如我,尽量参加学生社团,那种偏技术的部门,在同一个部门里面一起奋斗的感觉非常好。

学习环境

  • 在搜索引擎上面多走几步

很多时候我们遇到问题,google一下解决了就完了,我们要多问几个自己几个问题,这个问题为什么产生,如何避免,下一次还会遇到吗,还有及时收集自己的问题多总结,你要知道圣斗士之所以那么牛是因为人家从不在跌倒的地方跌倒第二次,你要知道bug不是我们的试卷,bug是我们的成神的补丁

  • 培养开源精神

github没事都上去溜达溜达,看到好的项目可以跟进,看看人家的代码同你的有什么不同,开源不代表抄袭,任何创新都是从模仿开始,不要老想着搞个大新闻大项目,其实很多项目都是从小项目开始的

  • 多输出

其实很多人不知道,写东西也是学习的一种方法,因为很多时候我们学的东西有时候学的模拟两可,写出来有助你理清脉络,而且帮助后来者少走弯路,何乐而不为呢。

有些人说我没什么想写的,你找一些外国博客翻译也是可以的,通过翻译学习,一方面锻炼自己,一方面让更多人了解国外文化。

总结

成神是很难的,但是只要你在路上,不要回头就不难了。

看过好几把关于机器学习的书,但是很多书只是停留于算法原理阶段,或者更着重介绍算法原理, <<集体智慧编程>>这本书更多的是从实践来介绍书,比如你要撘一个推荐系统你怎么做,,还有怎么来做一个垃圾邮件过滤系统等….
接下来介绍一下我对于做一个推荐系统的理解.

前言

推荐系统是一个什么东西呢
简单来说就是两个字—-推荐

推荐系统的出现是伴随评价系统出现而出现的,评价系统就是我们的打分制,比如对一部电影每个人都对他进行打分

用户 电影A评价 电影B评价 新电影C评价 新电影D评价
张三 5.0 3.5 4.9 2.0
李四 2.5 5.0 2.0 3.0
王二 3.1 4.7

比如现在三个人张三李四王二,王二有两部电影没有看过,但是张三和李四看过了,现在就是要把电影C或D推荐给王二,推荐系统就是从其他数据分析来确定推荐次序

相似度

推荐系统如何根据其他用户的数据来推荐呢

这里就要介绍一个相似度的概念,也可以加权系数,对于张三和李四,这两个人都看过电影C和D,但是我们该听谁的呢,我们可以把两个人的评价加起来然后排序,但是假如张三和李四品味相差太多,而且张三的评价可能会把某个王二喜欢看的电影评价总值拉低

这个时候我们就要考虑一个相似度问题,就是尽量避免一些品味同王二不同的人对排名造成影响

这里我们引进相似度这个概念,我们看张三和李四对电影A、B同王二的差别,这里我们使用距离这个概念,我们使用距离公式 l=((r1^2 + r2^2+....rN^2)^1/n)

然后通过距离算得S(相似度)=1/(1+l)

当l=0时,两者相似度为1(最大),当l很大时,相似度几乎为零

用户 电影A评价 电影B评价 相似度
张三 5.0 3.5 0.23
李四 2.5 5.0 0.59

通过求相似度(权重)我们就把同我们臭味相投的人的评价提高了,那些同我们不一样品味的人评价作用降低了

选择

基于人物的评价推荐有个缺点就是人的评价有时候会比物品多,而且变化频繁,假如使用上面的方法的话,每次每一个用户评价过后,就要重新算一次相似度了,假如用户不多或者电影不多还好,一旦数据偏多服务器就扛不住了

所以我们要换一个角度出发,计算物品的相似度,这样图表就变成这样了

电影 用户-张三 用户-李四 相似度
A 5.0 2.5
B 3.5 5.0

这样有个好处就是可以离线处理数据,我们可以搭建一个离线处理系统,让一个系统专门处理数据,当服务器需要的时候再拿取过去.

总结

搭建一个简单推荐系统并不会你想象中那么简单,但是要搭建一个功能强大速度快的推荐系统需要的不仅仅是这么一点,还需要考虑系统的稳定和速度.

git是当今流行的版本控制工具,一般我们可能只要会push, pull就可以了,
但是当我们同别人共同工作的时候,我们必须要了解git协同开发的一些重要流程.

前言

git作为当今最流行的版本控制工具之一,当时开发出来就是为了管理Linux庞大源代码的分布式版本控制工具.
由于Linux源代码过于巨大,仅靠一个人的力量是完成不了的,那就必须把工作分配下去,然后将代码合并,所以git一开始设计的时候就是一种分布式的、多分支的

概念

所以git最重要的就是分支这个性质,分支是什么呢.

要了解分支必须要了解git工作原理.

git工作原理很简单就是addcommitaddcommit….,简单来说就是添加记录,添加记录,保存快照,添加记录,添加记录,保存快照

git工作分支流程-来源网上

如上图,随着master分支快照的一个一个建立,软件就慢慢的迭代下去了

分支工作流程

接下来我们要着重讲一下分支,我们看到master分支的v0.1版本,我们已经开发出稳定的v0.1版,这时候我们决定开发一个新功能.

在这里我们分了一个Develop分支,我们在Develop分支开发新代码.

这时候我们发现v0,1的一个bug,假如是没有使用版本控制的话,一般人会停下手中的活,然后从当前的新代码处来修复这个bug,当这个bug很简单的时候,我们不会遇到很大困难,但是当bug藏的很深,而且新代码隐藏了这个bug,或者被这个bug影响,这时修复工作就变得很困难.

还好我们有git,我们从v0.1直接分一个Hotfix分支,这两个分支的父都是v0.1,我们直接从稳定版本修复,不牵涉到新代码,这样修改好后我们就能很快从Develop分支继续工作了

而且这样有一个好处我们将masterDevelop分支合并的时候很大可能不会产生冲突.

冲突(coflic)是什么了,怎么能避免呢?

从两个分支的父亲v0.1看起,我们每次改动一个保存文件就会产生一个modify(修改)的动作,我们假如分支里面都对同一个文件产生了modify(修改)动作,当我们合并的时候这就是一个冲突,git无法理解采用哪个分支的modify动作,这时候就要你人工来修改采用哪个分支.

假如没有相同的文件有modify(修改)动作,git就会聪明的知道采用每个分支的最新的modify(修改)修改出一份所以文件的最新版.

那我们怎么来避免这个冲突呢,这就要求我们分支要分的合理,分支只要完成特定的工作,不要越俎代庖,那有些人会说我这个分支一定要改父的耦合地方否则我的代码工作不了,这时我们要好好思考自己的分支的功能,把合并耦合的代码放在主分支里面,次分支只要完成特定功能就可以了,这样合并分支时候不但可以安全的merge(合并)了, 而且修改bug的时候也可以对症下药,直接在问题开始的地方修改.

在牛客网刷了16道题了,在这做个总结

概况

编程题无非两种一种考算法,一种考数据结构

算法的话,考验你对事情的分析程度和脑袋的灵光,用好的算法还是又大又重的算法,用算法复杂度来看,一般能到 o(n)就算勉强可以,当到了o(n * n)你就要考虑是不是你算法有问题了.

数据结构的话,队列和链表和二叉树是比较常见的,当然有些奇怪的一般算法反倒很简单.

算法

谈算法的话不得不谈递归了,递归其实我感觉更想一种思想

话不多说请看题,斐波那契数列一直是递归的代表

                      0                 n= 1    
f(n) ={           1                   n= 2
            f(n-1) + f(n-2)     n>2

虽然