Quantcast
Channel: CodeSky 代码之空
Viewing all 195 articles
Browse latest View live

JavaScript 多维数组转一维数组

$
0
0

面试时还被问到多维数组如何转换成一维数组的问题,这个问题比较灵活,其实也有很多种解法,当然,考虑到面试时候比较紧张,还是踏踏实实的用传统的方法来,和深拷贝一样,依旧是使用递归。

由于没有额外需要考虑的东西,因此这题实际上比起深拷贝来说要简单许多:

var convert = function(arr) {
    var newArr = [];

    arr.forEach(function(val) {
        if (val instanceof Array) {
            Array.prototype.push.apply(newArr, convert(val));
        } else {
            newArr.push(val);
        }
    });

    return newArr;
};

push可以一次push多个arguments,可是我们传数组的话比较方便,那么我们想到的就是使用apply去把需要传入的一系列参数变成一个参数数组,这样的好处是,本来我们需要拼接数组进行连接(因为返回的是Array,现在我们只需要把数组传入就可以了)。

这个方法比较简单,也比较好理解,但是其实还有一种偷懒的方法,非常方便,但由于其精简的取巧,也有一些不方便的地方,但是看到了之后保证吐血:

/**
 * 这种方法的缺陷在于全部都会变成String
 * @param arr
 * @returns {Array}
 */
var simpleWayToConvert = function(arr) {
    return arr.join().split(',');
};

是不是觉得简单爆炸了?这种方法缺点很明显,所有的元素都被分为了String类型,如果原来是数字,还需要后续操作就比较不便了。


JavaScript 说一个排序算法

$
0
0

依旧还是面试题,比较麻烦的是,这个排序的关键在于,我们需要时间复杂度为O(n),空间复杂度为O(1)。

题目大概是这样的,看了输入输出基本就会明白了:

input: arr = ['', 'a', 'b', '', 'a', 'c', '', 'm']
output: [ '
', '', '', 'a', 'a', 'b', 'c', 'm' ]
问题描述:将数组中的字母与星号分开

这里其实主要没做出来是因为我没有很好地理解怎么计算复杂度,尤其是空间复杂度,时间复杂度我勉强记忆成for循环的嵌套还是可以的,但是空间复杂度呢?

这里空间复杂度意味着没有长度为n的数组(但是可以是常量个)。

括号里的当时是我没想到的部分,我以为是:一个数组都不能开。

那么问题也就迎刃而解了,使用类似于桶排序的方法即可:

var sort = function(arr) {
    var counter = new Array(27),
        index = 0,      // 计数数组的索引值
        startAscii = 'a'.charCodeAt(0),
        arrIndex = 0;   // 代排序数组的索引值

    // 首先,统计出现频率
    arr.forEach(function(elem) {
        index = (elem == '*') ? 0 : elem.charCodeAt(0) - startAscii + 1;

        if (isNaN(counter[index])) {
            counter[index] = 1;
        } else {
            counter[index]++;
        }
    });


    index = 0;

    // 然后,将统计完毕的值依次注入原数组,这样就能保证O1了
    while (index < 26) {
        if (!counter[index] || counter[index] == 0) {
            index++;
            continue;
        }

        counter[index]--;
        if (index == 0) {
            arr[arrIndex++] = '*';
        } else {
            arr[arrIndex++] = String.fromCharCode(index + startAscii - 1);
        }

    }

    return arr;
};

Python 从多人聊天室开始谈系列 - 起始

$
0
0

这次的操作系统课设要做聊天室,众所周知的是,Node.js家有强大的socket.io,官网的demo就能让你分分钟速写聊天室——但是并没有什么卵用。

从操作系统的角度,我们决定从底层出发,去挖掘一下究竟一个轮子是怎么样实现的。

首先先决条件,我们需要学习:

  • Python基础
  • Socket(TCP)知识
  • Thread(多线程)
  • GUI(Tkinter)

    目标是最快的速度学习并实现我们的一个简单而且比较稳定的聊天室。

![多人聊天模式](media/%E5%A4%9A%E4%BA%BA%E8%81%8A%E5%A4%A9%E6%A8%A1%E5%BC%8F.png)

我们首先先来简单的了解一下Socket的模式:一个Client与一个Server建立连接,那么多个Client的情况,就需要把Server作为转发器来进行消息的转发,然后由Server推送到另一个Client中。

在GitHub中有完整的代码,接下来的介绍也是按照我的版本迭代顺序来的=v=:https://github.com/csvwolf/sky-chatting-room

Python 从多人聊天室开始谈系列 - Socket

$
0
0

接下来我们首先略过了Python的基础,这一部分,随便找一本Python的书看看就行了,习惯了没有;的人生之后,在必须的语句里记得加:,基本上你就进入了Python模式。

当然,由于赶时间,这里很多可能不是最优写法,大家可以去GitHub提出=v=。

官网Demo有云:

# Echo server program
import socket

HOST = ''                 # Symbolic name meaning all available interfaces
PORT = 50007              # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)
conn, addr = s.accept()
print 'Connected by', addr
while 1:
    data = conn.recv(1024)
    if not data: break
    conn.sendall(data)
conn.close()
# Echo client program
import socket

HOST = 'daring.cwi.nl'    # The remote host
PORT = 50007              # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.sendall('Hello, world')
data = s.recv(1024)
s.close()
print 'Received', repr(data)

看不懂,别急,我们慢慢来,首先看Server部分,我们首先初始化好服务器并且传入主机(Host)和端口(Port),s.listen()表示你的队列能有多长,s.accept()是一个阻塞式函数,关于什么是阻塞,之后我们就能感受到了,去监听客户端发来的连接。

recv()表示一次获取字符的最大长度,而sendall()则是发送数据,至于sendall()send()的区别,在于一次性发送与分批逐步发送,换句话说,如果你需要使用send()去发送,可能需要一个循环来达成。

接下来在Client中,我们使用connect()去连接,其他函数相同。

这里值得额外说明一个无关的函数repr(),如果你之前没有接触过Python,可能会比较陌生,总之,你可以把它看成一个转换为字符串的函数。

当然,我们要把它转换为我们上一篇文章中所示的模型,还是需要下一点功夫的。

首先,我们就会发现,Client和Server都已经自动断开了,这显然不符合我们的需求,我们用一个while循环来保持他们。

我们的Client需要接收输入,这里我们用Python2,简单的来使用raw_input来处理,后期使用GUI来包装我们的程序之后会改动。

import socket

s = socket.socket()

host = socket.gethostname()
port = 1234

s.connect((host, port))
print s.recv(1024)
s.sendall('Hello World')
while 1:
    word = raw_input('>')
    s.sendall(word)
    print s.recv(1024)

Client改成了这样,就差不多成型了,当然使用之后我们才会发现很多问题。

接下来我们需要编写Server,Server编写起来稍微复杂一些,首先先考虑转发问题,基本上是需要读取一系列Client对象了,所以我们构建一个数组。

接下来我们遍历列表并且转发即可。

于是我们的Server改成了:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket

s = socket.socket()

host = socket.gethostname()
port = 1234
s.bind((host, port))

lists = []
s.listen(5)

while True:
    c, addr = s.accept()
    lists.append({'client': c, 'address': addr})
    print lists
    print 'Got connection from', addr
    c.send('Thank you for connecting')
    for client in lists:
        msg = client['client'].recv(1024)
        if msg:
            print msg
            for receiver in lists:
                if receiver != client:
                    receiver['client'].sendall(msg)
    #word = raw_input('>')
    #for c in lists:
        #c['client'].sendall(word)

for client in lists:
    client['client'].close()

这样运行之后就发现改好了,但是触发条件很诡异,这是为什么,这就轮到了没说的阻塞还是不阻塞登场了,也是我们下一篇要说的,涉及到多线程的问题。

Python 从多人聊天室开始谈系列 - 多线程

$
0
0

完成了上一个版本,我们会发现,根本聊不起来啊!

问题的关键在于,我们现在的程序,一次只能干一个事情,你让我等待输入了,我就不能好好输出了。

所以我们需要在此引入多线程的概念,多线程的概念,简单的来说,就是,我因为只有一个人,你让我去干一件事还可以,两件事我不行,那多加一个人,总可以干了。

那么阻塞呢,意思就是说:由于我干了这个,不能干那个,我们把这个现象叫做阻塞。

概念都理解了之后,我们知道了,只要多加一个线程就行了!

实现多线程有几种方法,具体来说,我们可以看一下这篇:
http://www.cnblogs.com/tqsummer/archive/2011/01/25/1944771.html

这个基本上和Java的差不多,Java是两种方法,一种继承类,一种继承接口。两种方法都可以,但又有所不同,Python中,我们使用threading,可扩展性更好。

基本步骤是:

scanner = scanThread() # 创建

scanner.start() # 启动

问题不大,在线程内部作出处理也很简单,我们简单分成接受和发送两个线程。Server与Client相同,这里就不贴完整代码了,以Server为例。

……以下在拷贝过来的时候缩进好像有点乱,发现无法运行的情况大家自行纠正以下……

class scanThread(threading.Thread):

    def __init__(self):

            threading.Thread.__init__(self)


                def run(self):

                        while True:

                                    c, addr = s.accept()
                                    
            lists.append({'client': c, 'address': addr})

                                                receiveThread(c, addr).start()
                                                
            print lists
                                                
            print 'Got connection from', addr
                                                
            c.send('Thank you for connecting')


class receiveThread(threading.Thread):

    def __init__(self, client, address):

            threading.Thread.__init__(self)

                    self.client = client

                            self.address = address


                                def run(self):

                                        while True:

                                                    msg = self.client.recv(1024)

                                                                if msg:

                                                                        print msg

                for receiver in lists:

                    if receiver['address'] != self.address:

                        receiver['client'].sendall(msg)

这里我们大致的流程是,每次有一个握手,就新建一个线程,当然,这里暂时没有涉及到结束线程以及后续处理,是最简单的模型,也是非常容易崩溃的,我们将在之后进行改良。

Python 从多人聊天室开始谈系列 - Tkinter GUI

$
0
0

接下来我们差不多能聊起来了,剩下的就是解决上一篇中我们遗留的登出移除问题以及做一个界面,那样我们就能给更多的人用啦。

选择Tkinter,主要是,作为一个没有其他语言GUI基础的人,入门最简单粗暴的方法可能就是这个了。

关于Tkinter,网上的资源其实说不上太多,还是比较难找的,尤其是对于一个写惯了HTML/CSS的,其实是挺痛苦的。

Tkinter的布局教程可以看这里:http://effbot.org/tkinterbook/grid.htm

具体的组件可以见:http://www.tutorialspoint.com/python/python_gui_programming.htm

看完了之后,我们简单的选择组件进行操作。

root = Tk()
text = Text(root)
text.insert(INSERT, "Hello.....")
text.insert(END, "Bye Bye.....")
text.grid(row = 0, columnspan=15)

这里我们用grid布局,可以见上述的连接1,有详细的grid布局解释。

当然,我们的聊天界面是不应该允许编辑的,但是在Tkinter的组件中,不允许编辑意味着连使用代码都无法插入,所以我们需要在插入时开启,在结束后关闭。

text['state'] = NORMAL
text.insert(END, '\n' + word)
text['state'] = DISABLED

然后,我们还需要输入框。

userLabel = Label(root, text='UserName')
userLabel.grid(row=1)
userInput = Entry(root)
userInput.grid(row=1, column=1, columnspan=8)

messageLabel = Label(root, text='Message')
messageLabel.grid(row=2)
messageInput = Entry(root)
messageInput.grid(row=2, column=1, columnspan=8)
postMessageBtn = Button(root, text='POST', command=postMessage)
postMessageBtn.grid(row=2, column=9)

画完之后,我们需要用get()获取数据,在未来的版本中,我们还将更新提交后删除输入框内文字的功能。

def postMessage():
    text['state'] = NORMAL
    text.insert(END, '\n' + userInput.get() + ': ' + messageInput.get())
    s.sendall(userInput.get() + ': ' + messageInput.get())
    text['state'] = DISABLED

当然,除了GUI,我们还需要额外的线程去处理接收,这个基本不用修改,和上一个版本一致:

class receiveThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        while 1:
            word = s.recv(1024)
            if (word):
                text['state'] = NORMAL
                text.insert(END, '\n' + word)
                text['state'] = DISABLED

main的部分,实现一个调用窗口,之后再加上一句,在窗口关闭时结束连接:

root.mainloop()

s.close()

这样GUI就差不多了,实现了简单的收发和显示。

接下来我们要解决由于退出了客户端而没有删除连接List中的值导致报错的问题,这里提供最简单的解决思路:

print self.address, ' -> ', receiver['address']
try:
    receiver['client'].sendall(msg)
except Exception, e:
    print '已登出该用户'
    lists.remove(receiver)
    print lists

像这样,lists.remove(elem),就能删除元素了。remove中传入的不是index,而是element,这是个很方便的特性,因为这样我就能保证操作的原子性了,否则的话,我们可以想象到:首先我们找到index,然后在操作,如果多线程就会遇到同步问题了。

这样,GUI的部分也基本完成了。

备注:如果无法输入中文,可能是Tkinter版本过低,请升级。

Python 从多人聊天室开始谈系列 - 线程同步与读者写者模型

$
0
0

接下来,我们不可避免的会遇到线程同步问题,这是因为我们涉及到了共享数据的问题(也就是一个数组)。

我们来看看Python的锁:

threadLock = threading.Lock()

在同步的地方,用:

threadLock.acquire()
lists.append(receiver)
threadLock.release()

进行锁的增加和释放。

锁是一种非常简单的工具,但锁的使用决定了你多线程的运行效率。

因此,我们在此介绍一种模型:读者写者模型,来解决效率的问题。

读者写者模型,简单的来说就是:有读者的时候不允许写入。

模型的概念可以看这里:http://c.biancheng.net/cpp/html/2601.html

这里我们写成Python版就行了:


class scanThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) 
 def run(self): while True: c, addr = s.accept() receiver = receiveThread(c, addr) receiver.setDaemon(True) threadLock.acquire() lists.append(receiver) threadLock.release() receiver.start() print lists print 'Got connection from', addr # c.send('Thank you for connecting') class receiveThread(threading.Thread): counter = 0 
 def __init__(self, client, address): threading.Thread.__init__(self) self.client = client self.address = address 
 def run(self): while True: try: msg = eval(recv_msg(self.client)) except: break if msg: print msg if msg['type'] == 'message': mutex.acquire() if receiveThread.counter == 0: threadLock.acquire() receiveThread.counter += 1 mutex.release() for receiver in lists: if receiver.address != self.address: print self.address, ' -> ', receiver.address try: send_msg(receiver.client, repr(msg).encode('utf8')) except Exception, e: lists.remove(receiver) mutex.acquire() receiveThread.counter -= 1 if receiveThread.counter == 0: threadLock.release() mutex.release() elif msg['type'] == 'command': if msg['content'] == 'quit': threadLock.acquire() lists.remove(self) threadLock.release() break

完整的代码如上。

这里有一部分如send_msg函数是为了解决下一篇我们要说的TCP传输问题所封装的函数,而encode则是为了解决中文传输乱码的问题。

Python 从多人聊天室开始谈系列 - 说说TCP

$
0
0

接下来我们开始说说TCP的问题,众所周知的,三次握手和四次挥手是核心指导方向,但对我们而言,更重要的是,我们应该知道,TCP,或者说socket有些什么问题:

粘包和拆包问题。

这是比较主要的一个问题,当然,如果我们使用了WebSocket,那么基本不需要考虑这个问题。

主要是由于在TCP传输过程中不是一口气把所有信息都传输过去,而是分块传输的,所以会导致一系列问题,因此我们需要自己封装一下。

在这里有详细的代码:http://stackoverflow.com/questions/17667903/python-socket-receive-large-amount-of-data

我们稍作修改和封装之后即可使用。

之后我们还要做的是定义传输格式,这里我们简单的用json来传输,json需要json的拆装包函数:

json.loads()
json.dumps()

来实现json到Python字典类型的转换。

全部完成之后还有一个问题,我们这个模型,必须要保持长连接,但是在很长一段时间的没有消息过后,连接会被断开,我们需要重新建立连接,但是对于聊天室而言,我们还需要保证长时间连通,这样可以保证窥屏用户的需求。

于是我们进行心跳模拟,客户端一段时间发一个数据包给服务端,服务端一段时间应答即可。

当然,还是比较消耗资源的,如果可以的话,尽可能的还是不要用这么原始的方式。


Python 从多人聊天室开始谈系列 - 加密传输

$
0
0

最后,我们来说说加密传输,在传输中,如果我们中间被截获,那么消息将非常不靠谱,同时,伪造传输也将变得容易,而加密之后,安全性就能极大的提高了。

简单的加密,就选择对称加密。

对称加密而言,是对于客户端和服务端,采用同一密钥去加解密,所以实现起来比较方便,而如果使用非对称加密,就涉及到了公钥私钥的问题。

加密看这里:http://blog.csdn.net/xiaokfc/article/details/46873221

简单调用一下就行了,如果报错,需要安装一个库:

pip install pycrypto

完成了以上全部步骤,一个聊天室的基础建设就差不多完成了。

CentOS Ghost安装教程(PM2+Nginx)

$
0
0

前天有个朋友联系我说安装Ghost的时候遇到了问题(Nginx),想让我帮忙看看——我没配过啊,但是这正好是一次机会,玩玩除了PHP以外的东西。

好了,废话不多说,于是昨天我就尝试了一把,踩了一些坑,这里做个总结性发言,顺便表扬一下Ngnix反代大法。

看完本文,基本上你会知道Ghost的搭建,PM2的使用,Nginx的配置。

什么是Ghost?

Ghost是一个基于Node.js的博客程序。

Ghost 官方推荐使用 Node 0.10.x 版本,同时支持 Node 0.12.x 和 4.2.x 版本。

安装Ghost

首先先需要安装Node.js,安装Node.js教程太多,这里不再赘述。过去写过一篇编译安装Node.js可供参考(版本号自己改:http://codesky.me/archives/centos-nodejs-install.wind

接下来下载Ghost,除了官网下载之外,这里推荐一下:http://www.ghostchina.com/download/

我下载了中文标准版,上传到服务器之后解压缩:unzip -uo ghost.zip -d ghost

接下来切换到Ghost目录:cd /你的 Ghost 解压目录

npm install --production来进行安装。

Ghost默认选用的是SQLite,如果你需要搭配其他数据库(比如MySQL),见此参考链接:在CentOS 系统上搭建安装 Ghost博客。如果你是新手,不建议对MySQL/Nginx等做过多修改(避免Boom),可以之后在自己的虚拟机之类的地方折腾积累经验,毕竟博客重要的是写文,所以那篇……看看就好。

这里顺便友情提示,MySQL的日志文件查看:less /var/log/mysqld.log

如果你的MySQL无法启动,第一时间看看日志文件。

全部完成之后npm start,注意,此时启动是使用的默认的development环境,我们上线时,肯定用的是production,development可供调试和二次开发等。

默认会运行在:127.0.0.1:2368中,通过127.0.0.1:2368/ghost可以访问后台(初始化博客)

配置Ghost

如果测试无误,也就是说我们安装完毕了,下一步就要开始配置Ghost了,其实也没什么好改的,在production里把url修改为你需要绑定的域名或IP即可(此处似乎与后台传送门相对应,因此需要修改),其他可以不用动。

之后我们把Node改为production环境

echo "export NODE_ENV=production" >> ~/.profile
source ~/.profile

配置PM2

如果你还没有安装过,那么接下来先安装PM2:npm install -g pm2

此处可见教程:Node.js PM2 愉快部署Node.js

选择PM2是因为其方便快捷,你也可以选用其他服务,在本文参考链接中将给出。

于是pm2 start index.js -n ghost

配置Nginx

如果你还没有安装过,可以参考:CentOS yum配置LNMP服务器(Nginx+PHP+MySQL)

在CentOS中,尤其注意需要配置iptables打开80端口,否则外部无法访问。(当然高版本的CentOS7貌似是没有iptables的?如果是Ubuntu,我记得也是没有这个的)

配置Nginx反向代理,使其通过80端口访问,日后我们可以绑定多个端口,过去也有博文:CentOS Nginx反向代理 + Apache配置

由于这里我们不用配置Apache,所以步骤没有那么多:
vi /etc/nginx/conf.d/default.conf

server_name一行后加入(大致可以参考上文):

    location / {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host      $http_host;
        proxy_pass         http://127.0.0.1:2368;
    }

基本上就变成了:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host      $http_host;
        proxy_pass         http://127.0.0.1:2368;
    }
}

当然,如果我们需要配置多个子目录,也很简单:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host      $http_host;
        proxy_pass         http://127.0.0.1:3000;
    }

    location /ghostblog {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host      $http_host;
        proxy_pass         http://127.0.0.1:2368;
    }
}

虽然我们不懂Nginx,不过这里很简单就能看出来,做反向代理还是很简单的,这样可以节约对外映射的端口数,也更加友好——试想一下别人看到你莫名其妙端口号的表情。(由于我的VPS是配置过PHP的,通过这种方法也能够让PHP和Node程序共用端口——其实是反向代理,非常爽)如果你的Node或者其他程序还在不断的占用你的端口们,请快点用Nginx解脱你的对外端口吧(Apache亦可,不过性能Nginx更好)。

配置完成,重启Nginx:/etc/init.d/nginx restart

参考资料

MySQL安装:

CentOS yum配置LNMP服务器(Nginx+PHP+MySQL)

CentOS 编译安装MySQL5.6.17

PM2:
Keep Ghost Running with pm2

其他:

Ghost部署与安装全教程

JavaScript 说说随机排序(洗牌程序)

$
0
0

在小鱼的gayhub看到了这样的一题,其中就需要用到洗牌程序,当然,我在看到排序算法的时候就想到这样可以用于排序,所以很快就有了第一个排序算法:

var shuffleHack = function(arr) {

    var result = arr.slice();

    result.sort(function() {

        return Math.random() - 0.5;

    });


    return result;

};


之所以说这是一种Hack方法,在于他的代码精简,但是实际上,这不是一个很正确的洗牌程序,在于他的随机性很差,具体我们可以用统计学的方法去统计结果,这里忽略不计(其实是我也不太清楚怎么统计比较科学)。

有一种比较科学的洗牌程序,叫做Fisher Yates算法,关于Fisher Yates的介绍,可以看Wiki:Fisher–Yates shuffle

在这里我们根据其伪代码(可以看出这是一个倒序的选择排序算法):

-- To shuffle an array a of n elements (indices 0..n-1):
for i from n−1 downto 1 do
     j ← random integer such that 0 ≤ j ≤ i
     exchange a[j] and a[i]

给出具体实现即可:

/**
 * 正确的做法
 * Fisher Yates 算法
 *
 * 原理是倒序选择排序,只是随机选择无序的位置进行交换排列
 * @param arr
 * @returns {Array.<T>|string|Blob|ArrayBuffer}
 */
var shuffle = function(arr) {
    var result = arr.slice(),
        length = result.length,
        i,
        temp,
        j;

    for (i = length - 1; i > 1; i--) {
        j = parseInt(Math.random() * i);
        temp = result[i];
        result[i] = result[j];
        result[j] = temp;
    }

    return result;
};

这样的随机性更强,基本可以称之为一个洗牌程序,在如何测试洗牌程序一文中对此亦有介绍。

最后,关于小鱼的题目:

一群人出去玩,写一个程序随机分组可以如何分。最后简化成 10 个人出去玩,如何将人随机分配到 4 个组里,并保证每个组的人比较均匀。

我当时用的洗牌的实现是:

var arr = [1,2,3,4,5,6,7,8,9,0];
var group = [[],[],[],[]];

(function split(arr, group) {
  // you code here
  var groupSize = group.length,
      totalSize = arr.length;

  // 随机排序
  arr.sort(function() {
    return Math.random() * 2 - 1;
  });

  // 接下来保证每组数量比较平均即可
  group.forEach(function(elem, index) {
    var size = Math.floor(totalSize / groupSize);
    for (var i = 0; i < size; i++) {
      elem.push(arr.pop());
    }

    totalSize -= size;
    groupSize--;
  });

  console.log(group);
})(arr, group);

其实纯属想多了,为什么要那么麻烦呢——

可见小鱼对于此分配时的写法:

'use strict'

var arr = [1,2,3,4,5,6,7,8,9,0];
var group = [[],[],[],[]];

(function split(arr, group) {

  var size = group.length; // 分多少组
  var length = arr.length; // 人数总长度

  // 每次随机叫一个人,跳进一个组里,并从原数组里踢掉
  while(arr.length) {
    for(let i = 0; i < length; i++){ 
      let index = Math.random() * arr.length | 0;  // 随机
      group[i % size].push(arr[index]);            // 入组
      arr.splice(index, 1);                        // 踢掉
    }
  }

  console.log(group);
})(arr, group);

另外,他还补充了一则对于Fisher Yates算法的补充阅读:https://bost.ocks.org/mike/shuffle/

感觉似乎又Get了新技能。

JavaScript 排列与组合

$
0
0

排列与组合在我们日常生活中其实也是挺常用的,包括在算法中,也算是比较实用的东西,但是排列组合也是一个难点,这里我们先从简单的出发。

最简单的是全排列和全组合,其中组合的思路更加简单易行,因此我们先从全组合说起。

在说之前,我们先来确定一下定义(数学渣只能从自己下定义开始以免误导读者),我们的全排列,指的是PNN,也就是有n个数字,坑也有n个,求全部的排列情况;全组合,则是除了空集外,n个字母能组成多少种可能性(不一定要全部使用)。

由此,我们开始了我们的全组合之旅:

首先,我们考虑,假设有n个字母,我们将其使用标记为1,没有使用过标记为0.则可以得出[a, b, c] [0, 0, 1]=> [c]。那么同理,我们很快就能够列出所有的情况,就是把1和0全部尝试一遍,那么当abc时,数组为全1,此时,其实我们就能够想到,跟二进制非常像,全1时为7,全0为0。

那么问题就简单了,就是把全组合转换为到n的二进制,将二进制对应的结果输出:

简单列表来说明问题:

c b a
0 0 1 => a
0 1 0 => b
0 1 1 => ab
1 0 0 => c
1 0 1 => ca
1 1 0 => cb
1 1 1 => abc

程序也比较简单:

var combinate = function(listArr, callback) {
    var result,
        temp,
        left,
        max = Math.pow(2, listArr.length),
        i;

    for (i = 1; i < max; i++) {
        temp = i;
        result = listArr.filter(function() {
            var result = false;
            left = temp % 2;
            if (left !== 0 && temp !== 0) {
                result = true;
            }
            temp = parseInt(temp / 2);

            return result;
        });

        callback(result);
    }
};

filter过滤数组结果,只有为true时才进入结果数组。

当然,这个明显没有考虑去重,没关系,接下来我们会在之后的程序中考虑去重的,先进入简单的排列,排列的思想是递归的交换前一项和后一项,算法比较神奇,但如果我们在草稿纸上仔细写下来,会发现真的是。之后就好办多了:

/**
 * 交换一个数组中的第i项和第j项(起始项为0)
 * @param arr
 * @param i
 * @param j
 */
var swap = function(arr, i, j) {
    var length = arr.length,
        temp;

    temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
};

/**
 * 全排列使用的是递归交换数组中的数字
 * @param listArr
 * @param start 开始的值,调用时输入0
 * @param callback
 */
var permutate = function(listArr, start, callback) {
    var i,
        length = listArr.length;

    if (start === listArr.length - 1) {
        callback(listArr);
    } else {
        for (i = start; i < length; i++) {
            swap(listArr, start, i);
            permutate(listArr, start + 1, callback);
            swap(listArr, start, i);
        }
    }
};

循环中第二次交换是为了还原现场。

之后我们考虑除了[a, b, c]之外,还有可能是[a, b, b]的情况,因此我们需要对过程或者结果中进行去重,这里先从过程去重开始考虑。之后我们会说结果去重。

过程去重的算法主要考虑在交换过程中选中了相同内容,不同位置的字符,因此只要只选中他们中的一个,忽略其他即可,通常来说我们都选择特殊位置,第一个或者最后一个,那么只要判断是否是第一个或者最后一个即可。

/**
 * 检测是否可以交换
 * 查看交换的值在之后是否有重复出现,需求是只和特定的value仅交换一次,如果有重复则不交换
 * 此时有两种思路,一种是首次交换,之后抛弃所有同value的值(此时我们可以提供向前检测的方法)
 * 第二种是最后交换,在此之前所有值都不进行交换(这时我们使用向后检测的方法)
 * 这里我们使用向后检测,保证这是最后一个
 * @param arr
 * @param i
 * @param j
 */
var canSwap = function(arr, i, j) {
    return arr.slice(i, j).indexOf(arr[j]) === -1;
};

之后,在全排列中我们加入判断,即可达到去重的效果:

/**
 * 去重的全排列
 * @param listArr
 * @param start
 * @param callback
 */
var permutateWithoutTheSame = function(listArr, start, callback) {
    var i,
        length = listArr.length;

    if (start === listArr.length - 1) {
        callback(listArr);
    } else {
        for (i = start; i < length; i++) {
            // 检测是否需要交换
            if (canSwap(listArr, start, i)) {
                swap(listArr, start, i);
                arguments.callee(listArr, start + 1, callback);
                swap(listArr, start, i);
            }
        }
    }
};

这样挺直观,缺点是效率很低,毕竟每次都要找index,相当于循环了一遍,相当耗时,在结果去重中,我们将选择更快的方式。

先来说说一般的排列和组合,我们发现,排列或者组合,这个程度的还是不足以满足我们的需求,毕竟平时组合我们可能是固定个数的,排列也不一定要求全排列。

还是先从组合开始,我们依旧从二进制下手:

可以来看一下这一段:

  1   2   3   4   5
  1   1   1   0   0   //1,2,3     
  1   1   0   1   0   //1,2,4     
  1   0   1   1   0   //1,3,4     
  0   1   1   1   0   //2,3,4     
  1   1   0   0   1   //1,2,5     
  1   0   1   0   1   //1,3,5     
  0   1   1   0   1   //2,3,5     
  1   0   0   1   1   //1,4,5     
  0   1   0   1   1   //2,4,5     
  0   0   1   1   1   //3,4,5  

 
以上是他的全组合,我们可以看到,找到1前面的0,与其交换,把其他1归于从0开始的最左端,实现一次循环。

看懂了这个,接下来就能做了:

/**
 * 部分组合
 * 我们不需要全部的组合,只需要指定selectNum个空去进行组合
 * @param listArr
 * @param selectNum
 * @param callback
 */
var combinateWithoutAll = function(listArr ,selectNum, callback) {
    var listCount = [], // 计数器数组
        result = [],
        length = listArr.length,
        oneCount = 0,   // 统计出现过的1
        i,
        canPrint = true;

    init(listCount, selectNum, length - 1);

    while (checkEnd(listCount, selectNum)) {
        store(listArr, listCount, result);

        if (canPrint) {
            callback(result);
        }

        canPrint = true;

        for (i = 0; i < length; i++) {

            if (listCount[i] === 0 && oneCount !== 0) {

                listCount[i - 1] = 0;
                listCount[i] = 1;

                // 去除重复的无用项
                if (!canSwap(listArr, 0, i)) {
                    canPrint = false;
                }

                init(listCount, oneCount - 1, i - 2);
                oneCount = 0;
                break;

            } else if (listCount[i] === 1) {
                oneCount++;
            }

        }
    }


    store(listArr, listCount, result);
    callback(result);

    /**
     * 对 0 / 1结构进行初始化
     * 前num个为 1
     * 直到end为止为0
     * @param listCount
     * @param num
     * @param end
     */
    function init (listCount, num, end) {
        var i = 0;
        for (i = 0; i < num; i++) {
            listCount[i] = 1;
        }

        for (; i <= end; i++) {
            listCount[i] = 0;
        }
    }

    /**
     * 存储结果
     * @param listArr
     * @param listCount
     * @param result
     */
    function store(listArr, listCount, result) {
        var length = listCount.length,
            i,
            j = 0;

        for (i = 0; i < length; i++) {
            if (listCount[i] === 1) {
                result[j++] = listArr[i];
            }
        }
    }

    /**
     * 检查是否是最后一个组合情况
     * @param listCount
     * @param selectNum
     * @returns {boolean}
     */
    function checkEnd(listCount, selectNum) {
        var length = listCount.length,
            i = length - 1,
            end = length - selectNum;
        for (i = length - 1; i >= end; i--) {
            if (listCount[i] !== 1) {
                return true;
            }
        }

        return false;
    }
};

这里我们顺便实现了一个去重,如果不需要的话把去重的几行删掉即可(同时效率也提高了)。

对于排列,我们想到了一个办法:把组合进行全排列,将全排列函数作为回调传入即可。

另一种方法,对于全排列,选出每个排列中的前n项截断,之后进行去重。

这里我们说了半天的结果去重终于要登场了,我们使用一个利器:哈希表。在JavaScript也就是对象,我们把数组转换为字符串,作为index,然后把index取出即可。

/**
 * 利用全排列的思路,先截断后去重
 * @param listArr
 * @param selectorNum
 * @param callback
 */
var permutateWithoutAll = function(listArr, selectorNum, callback) {
    var resultHash = {},
        result = [],
        i = 0;

    permutateWithoutTheSame(listArr, 0, function(arr) {
        var index = arr.slice(0, selectorNum).join(',');
        resultHash[index] = '';
    });

    for (var index in resultHash) {
        if (resultHash.hasOwnProperty(index)) {
            result[i++] = index.split(',');
            callback(result[i - 1]);
        }
    }
};

效率比起原来肯定是高了不少,原来我们的去重也可以通过这个思路来处理。

之后我们用系统的time来统计了一下运行时间,发现大量时间都花在了去重上,确实在过程中去重还是很坑的,另外一点,由于是递归调用,容易stackoverflow,只能适合少量的排列,大量就不适合了,其他排列的方法以后有空再进行学习。

另外,如果只是需要一组随机字串,可以使用洗牌程序。

扩展阅读:

全排列和全组合实现

高效率的排列组合算法--《编程珠矶》--python实现

全排列的六种算法

模拟登录新手入门教程

$
0
0

阅读本文,你能得到一些Cookie和Session的基本知识,模拟登录的分析思路,但是具体代码请自行实现。

今天早上起来折腾了一下模拟登录,主要是以前从来没干过,以为很有难度——实际上并没有,折腾了大概一到两个小时就完全搞定了。

这次讲的主要是分析的思路,因为代码太难看了所以暂时不提供代码,等好看了在放出。

我们以上海海事大学数字校园平台为例,主要是他是比较简单的系统,为什么说简单,之后我们会做说明。

首先,我们来说一下本次使用的道具:

Firefox + Firebug
Postman
Node.js+superagent+cheerio(本文不需要)

之所以选择这些,是因为他们能让我们的测试和开发极大的简单化,当然你也可以使用类似的道具去做。

在模拟登陆之前,我们要先清楚一点:服务器是怎么判断用户登录状态的——如果你曾经开发过网页,一定会很快告诉我:用Session。

那么服务器又是怎么确定Session和客户端的呢,答案是Cookie,我们摘取一张图解HTTP的图来说明问题:

14632833866511.jpg

这张图非常好的说明了Cookie与Session的关系,明白了这一点,我们也就明白我们的中心主旨了:就是获取Cookie。

下一步,我们就来针对HTTP请求进行人工查看。

首先,来了解一下Firebug的使用(如果你不会呼出Firebug/审查元素面板,那你可能不在本文的读者受众中,请先去学习一些基础知识)

2016-05-15 at 11.26.png

2016-05-15 at 11.39.png

我们可以通过HTML按钮来过滤CSS和JavaScript请求,专注于页面加载本身,然后我们看到了Response中的Set-Cookie,在首次访问时,才会在响应头有这一项,这是由Cookie的特性决定的(所以方便起见我开了隐私预览窗口):

关于这一点,我们也可以看图解HTTP的图:

首次访问:

14632841880493.jpg

之后访问:

14632842181550.jpg

接下来我们来试试登录:

2016-05-15 at 11.51.png

等待完全载入完毕,我们会发现多了很多请求,一一来进行分析,主要还是看Request和Response,传入和返回的值的变化等。

这里我们从login开始,主要看三个面板:

2016-05-15 at 11.54-1.png

2016-05-15 at 11.55.png

2016-05-15 at 11.56.png

注意:由于前面我们说明过,登录的关键在于Cookie,所以Cookie不能随便泄露(因此此处打上各种马赛克)。

这里新的Set-Cookie我们不确定有没有用,我们看之后请求头的Cookie信息,现在我们先从这里看出来了,要发什么信息(Post),用什么类型(Content-Type),登录成功会返回什么(响应),这里我们用Postman试试。

2016-05-15 at 12.03.png

注意红色框出来的Content-Type。

然后我们Send之后会发现……咦,失败了——原因是lt不明,回到登录前的页面,我们去寻找lt是从什么地方传入的:

2016-05-15 at 12.06.png

看得出来,多半是一个随机校验码,这个未使用过的,我们把它复制进去,结果是可以的,与我们手动登录的返回内容相同。

但是我们记得,之后还有一个302跳转和一个200,才到了我们最终的目的地,接下来开始考证他们是否有用。

先看我们目的地的Cookie:

2016-05-15 at 12.10.png

似乎与首页的有些不太一样?

那么我们就怀疑是否是在跳转后的访问时顺便附带了点什么。

2016-05-15 at 12.13.png

我们成功找到了Cookie的来源,顺便一提,是用post中响应结果的跳转去获得的。

之后带着这个Cookie,我们能否成功访问,测试一下就知道了(答案是可以哟)

为什么带着Cookie仍然需要测试,这是由于为了避免爬虫、机器登录等情况,一些网站会进行验证User-Agent(比较常见),Referer等行为,此时我们需要对这些也进行设置,才能成功访问。

总结一下,我们这里教大家的其实是一个寻找答案的过程,当然,这是一个最简单的模拟登录,非常适合练手,如果遇到验证码,或者其他复杂情况,就不难么好做了。

superagent和cheerio的用法,这里暂时就不多做解释了,官方文档写的非常明白。

在模拟登录之后,我们就能做很多事情了,其中包括了自己做API、深度爬虫等等,诸君各凭脑洞,想象空间还是很大的。

OSX Wireshark 修复 No Interfaces Found

$
0
0

今天在研究Fiddler的替代产品的时候再次打开了WireShark发现了这个提示。大致是没有检测到任何接口,估计是权限问题,针对Mac下修改方案也算简单:

sudo chown $USER /dev/bpf*

结束。

客户端Session和服务端Session对比

$
0
0

引言

本来想作为Client Side vs Server Side Session的翻译,结果自己的英语太烂,怕翻译的太渣,只能自行根据几篇参考文章总结一下,如果有说的不对的地方,还请大家指出。(把Client Side Session翻译成客户端Session也让我觉得很别扭)。

故事背景是昨天和受泽讨论验证码如何进行验证,然后谈到了Flask(Python的一个MVC框架)使用的是Client Side Session,于是略作了解。

首先我们需要了解一下Session的工作原理,在文末的参考链接1有针对PHP的分析,可以供参考,上一篇模拟登陆中也节选了图解HTTP中的图片来说明一下问题,这是面试中的常见问题,我们之后再对比时也会做相应说明,这里稍微介绍一下,就是在客户端存SessionID之类的Cookie信息,每次访问把Cookie带去请求服务端,服务端进行验证操作,如果服务端有Cookie对应的Session信息,那么确认,把它拿出来使用即可。

那么既然如此,为什么又会分为Client Side Session和Server Side Session,他们的优缺点是什么——这是本文我们所想讲的内容:

Client Side Session

Client-Side-Sesion-Model.png
在Client Side Session中,所有的Session信息都存放在Cookie中,好处当然是你不用再每个节点都同步不断的更新数据,甚至是请求都少了许多(因为具体的数据已经存在Cookie中了,你可以直接读取,而不用进行服务端的IO操作),当然,我们首先得保证你的客户端中存放的数据是安全的(防止用户恶意篡改),也就是需要加密后进行存放。

之后如图,使用非对称加密,把私钥放在本地,服务端存放公钥,不同服务端间只要同步公钥就行(具体的内容存放在客户端中,因此修改不会频繁)。这样的话,无论是哪个客户端,都能找到任一服务端完成验证。

优点(基本都是围绕上文所述展开的):

  • 低延时,由于不用在服务端进行数据的获取,只需要验证和创建两个步骤,使得整个流程变得很快。
  • 故障而可能出问题的点更少,理由同上。
  • 高度扩展性
  • 任何分布式中的服务器都可以进行验证,而无需反复的同步操作

缺点:

  • Cookie的大小受限
  • 所有的信息都存放在客户端中,可能会导致信息泄露
  • 无法完整的终止(可以通过设定保质期来解决这个问题)
  • 一些功能如注销可能无法被完整的支持(就算移除了Cookie,再次提交之后依旧会出现)。

Server Side Session

Server Side Session也就是我们传统的模型,所有的数据都存放在服务器中,如果存在多个服务器同步,需要完整的同步Session,在客户端中存放的Cookie只记录与服务端的映射关系。

Server-Side-Session-Model.png

优点:

  • 按需随时可控的结束Session
  • Cookie的大小更小
  • 由于数据存放在服务器中,所以数据较为安全
  • 不用担心Cookie大小,想放多少数据就放多少数据
  • 由于存放在我们的服务器而不是客户端中,所以我们可以随时改变我们对Session的管理机制

缺点:

  • 更大的风险,如果存放Session的介质发生了故障,那么所有的Session都没办法访问。
  • 对于多个服务器而言,同步和验证上的困难

结语

对于这两种实现方式而言各有利弊,当然在Flask后续中,受泽尝试了将Session删除之后恢复Cookie,发现竟然依旧可以成功读取原值,大概所谓的不可终止也就是这个意思。

个人目前仍然倾向于传统的方法,似乎更为安全和可靠一点,一些部分由于没有切实的实践所以翻译或者说自己归纳的可能有些生硬,望大家多多指教。

本文参考

https://www.pureweber.com/article/how-session-works/

http://phillbarber.blogspot.com/2014/02/client-side-vs-server-side-session.html

https://hacks.mozilla.org/2012/12/using-secure-client-side-sessions-to-build-simple-and-scalable-node-js-applications-a-node-js-holiday-season-part-3/

https://en.wikipedia.org/wiki/Session_(computer_science)#Client_side_web_sessions


Node.js 几种获得正式绝对路径的方式

$
0
0

在项目中需要绝对路径去设置一些文件地址,于是搜索了一下,在这里大致总结一下:

console.log(__dirname);
console.log(__filename);
console.log(process.cwd());
console.log(path.resolve('./'));

我的项目路径为:/Users/SkyAo/Documents/Coding/JavaScript/Node.js/Connector,而运行的文件是在core文件夹中,然后执行项目路径下的index.js(其中引用了core目录中的appConfig.js

返回为:

/Users/SkyAo/Documents/Coding/JavaScript/Node.js/Connector/core
/Users/SkyAo/Documents/Coding/JavaScript/Node.js/Connector/core/appConfig.js
/Users/SkyAo/Documents/Coding/JavaScript/Node.js/Connector
/Users/SkyAo/Documents/Coding/JavaScript/Node.js/Connector

得到的返回值告诉我们:

  • __dirname返回的是执行的JavaScript文件。
  • __filename__dirname一样,返回的是执行的目标JavaScript文件。
  • process.cwd()返回的是项目的目录,也就是运行的JavaScript所在的目录,以下是官方解释。

process.cwd() Returns the current working directory of the process.

  • path.resolve('./')调用同process.cwd(),可以执行一系列路径操作后返回最终路径,官方说明如下:

Resolves to to an absolute path.
If to isn't already absolute from arguments are prepended in right to left order, until an absolute path is found. If after using all from paths still no absolute path is found, the current working directory is used as well. The resulting path is normalized, and trailing slashes are removed unless the path gets resolved to the root directory. Non-string from arguments are ignored.

以下样例,说明问题:

path.resolve('foo/bar', '/tmp/file/', '..', 'a/../subfile')

相当于:

cd foo/bar
cd /tmp/file/
cd ..
cd a/../subfile
pwd

区别是不同的路径可以是文件或者不存在。

当然,看手册的话,我们会发现Node Core中的path包还是相当强大的,今天就先介绍一下这个,当时我并不知道这么多种方法,是在项目目录把__dirname作为参数传入的。

最后,参考资料:

https://github.com/imsobear/blog/issues/48

Ubuntu/Python 结巴分词 + Word2Vec利用维基百科训练词向量

$
0
0

结巴分词是一个跨语言的中文分词器,整体效果还算不错,功能也够用,这里直接用Python了,其他主流语言版本均有提供。

Word2Vec,起源于谷歌的一个项目,在我刚开始接触的时候就关注到了他的神奇,大致是通过深度神经网络把词映射到N维空间,处理成向量之后我们终于可以在自然语言处理上方便的使用它进行一些后续处理。(具体的方法忘了)

Python的gensim库中有word2vec包,我们使用这个就可以了,接下来我们就对维基百科进行处理,作为训练集去训练。(包地址:http://radimrehurek.com/gensim/models/word2vec.html

本文参考:http://www.52nlp.cn/中英文维基百科语料上的word2vec实验

心酸安装史

开始确定完自己的思路其实是两周前,之所以一直没有动手,是因为不舍得在自己的Mac下跑训练,但是实验室的机子死活装不上,这里讲一下我的心酸遭遇。

首先按照教程装上pip,这个基本不成问题,在博客之前也有讲过类似的,如果需要可以搜索一下过去的文章。

依赖库是Numpy和SciPy,在Scipy的说明里有:

sudo apt-get install python-numpy python-scipy python-matplotlib ipython ipython-notebook python-pandas python-sympy python-nose

安装期间如果遇到问题,可以使用:

sudo apt-get install gcc g++

之所以使用apt-get,主要是pip install已经一把辛酸泪了还是不成功。

这样安装完成之后,继续pip install gensim,下载不动,换源再试,依旧安装失败,似乎是在编译时出现了问题,具体查了一下也没有查出什么问题,只是有人说手动安装成功了,那么我就试试手动安装吧。

手动安装就是在pip官网上把对应的包下载下来,然后sudo python setup.py install,结果似乎没什么问题。总算是安装上了。

打开python终端尝试import也能用,换言之我们总算可以用了。

如果你想着手解决一些报错的问题,可以参考一下在我解决之后找到的一篇文章,或许对你有帮助:http://ijiaer.com/win7-x64-install-gensim-solve-with-gcc-failed/

处理

使用维基百科的数据很方便,一是Wiki给我们提供了现成的语料库(听说是实时更新的),虽然中文体积不大,但比起自己爬来方便了不少。

如果使用英文那就更棒了,非常适合作为语料库。

当然只是说在通用的情况下,在专业词汇上,经过测试效果比较一般(考虑到专业词库有专业wiki,以及中文词条本身也不太多)。

首先,我们把Wiki处理成Text格式待处理的文本,这一步在本文参考中有现成的代码。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import logging
import os.path
import sys

from gensim.corpora import WikiCorpus

if __name__=='__main__':

    program = os.path.basename(sys.argv[0])
    logger = logging.getLogger(program)
    logging.basicConfig(format='%(asctime)s: %(levelname)s: %(message)s')
    logging.root.setLevel(logging.INFO)
    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
    logging.getLogger('').addHandler(console)

    logger.info("running %s" % ' '.join(sys.argv))

    #check and process input arguments
    if len(sys.argv) < 3:
        print globals()['__doc__'] % locals()
        sys.exit(1)
    inp, outp = sys.argv[1:3]
    space = " "
    i = 0

    output = open(outp, 'w')
    wiki = WikiCorpus(inp, lemmatize=False, dictionary={})
    for text in wiki.get_texts():
        output.write(space.join(text) + "\n")
        i = i + 1
        if (i % 10000 == 0):
            logger.info("Saved " + str(i) + " articles")

    output.close()
    logger.info("Finished Saved " + str(i) + " articles")

loggerprint更规范,过去没有用过相关的,不太会用,其实用起来还是蛮方便的,这里暂时就先不介绍了。

Wiki的处理函数在gensim库中有,通过处理我们可以发现,最终效果是变成一行一篇文章并且空格分隔一些关键词,去掉了标点符号。

执行:python process_wiki.py zhwiki-latest-pages-articles.xml.bz2 wiki.zh.text,等待处理结果,比较漫长,基本上接下来你可以随便做点什么了。

处理完成之后我们会发现,简体和繁体并不统一,所以我们需要用opencc进行简繁体的转换,这里不得不说BYVoid是个非常牛逼的同学。这个被官方收录了,我们可以直接用sudo apt-get install opencc来安装,github开源:https://github.com/BYVoid/OpenCC

然后执行:opencc -i wiki.zh.text -o wiki.zh.text.jian -c zht2zhs.ini

得到简体中文的版本,这一步的速度还可以。

分词

下一步,分词,原文中用的似乎有些复杂,结巴分词的效果其实已经不错了,而且很好用,这里就用结巴分词处理一下。本身而言结巴分词是不去掉标点的,但是由于上一步帮我们去掉了,所以这里我们比较省力(不然的话原本准备遍历去掉,根据词性标注标点为x)。

我的Python还是不太6,所以写的代码比较难看OTZ,不过效果是实现了,处理起来比较慢,我觉得readlines里的参数可以更多一点。

这里下面处理完了之后用map处理,拼接list并且使用utf-8编码,此外,保证一行一个文章,空格分隔(这是后续处理函数的规定)。

这里分词没开多线程,不过后来发现瓶颈似乎在读取的IO上。

#!/usr/bin/env python
#-*- coding:utf-8 -*-
import jieba
import jieba.analyse
import jieba.posseg as pseg

def cut_words(sentence):
    #print sentence
    return " ".join(jieba.cut(sentence)).encode('utf-8')

f = open("wiki.zh.text.jian")
target = open("wiki.zh.text.jian.seg", 'a+')
print 'open files'
line = f.readlines(100000)
while line:
    curr = []
    for oneline in line:
        #print(oneline)
        curr.append(oneline)
    '''
    seg_list = jieba.cut_for_search(s)
    words = pseg.cut(s)
    for word, flag in words:
        if flag != 'x':
            print(word)
    for x, w in jieba.analyse.extract_tags(s, withWeight=True):
        print('%s %s' % (x, w))
    '''
    after_cut = map(cut_words, curr)
    # print lin,
    #for words in after_cut:
        #print words
    target.writelines(after_cut)
    print 'saved 100000 articles'
    line = f.readlines(100000)
f.close()
target.close()

训练

最后就能愉快的训练了,训练函数还是参考了原文:

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import logging
import os.path
import sys
import multiprocessing

from gensim.corpora import WikiCorpus
from gensim.models import Word2Vec
from gensim.models.word2vec import LineSentence

if __name__ == '__main__':
    program = os.path.basename(sys.argv[0])
    logger = logging.getLogger(program)

    logging.basicConfig(format='%(asctime)s: %(levelname)s: %(message)s')
    logging.root.setLevel(level=logging.INFO)
    logger.info("running %s" % ' '.join(sys.argv))

    # check and process input arguments
    if len(sys.argv) < 4:
        print globals()['__doc__'] % locals()
        sys.exit(1)

    inp, outp1, outp2 = sys.argv[1:4]
    model = Word2Vec(LineSentence(inp), size=400, window=5, min_count=5, workers=multiprocessing.cpu_count())

    model.save(outp1)
    model.save_word2vec_format(outp2, binary=False)

这里用了一个个LineSentence函数,官方文档:http://radimrehurek.com/gensim/models/word2vec.html

文档这么说:

Simple format: one sentence = one line; words already preprocessed and separated by whitespace.
简单的格式:一句话 = 一行,预处理过并且用空白符分隔。

这里我们一篇文章等于一行。

执行训练:python train_word2vec_model.py wiki.zh.text.jian.seg wiki.zh.text.model wiki.zh.text.vector,训练速度也还可以。

之后我们就可以根据这个进行Word2Vec相关操作了:

In [1]: import gensim
 
In [2]: model = gensim.models.Word2Vec.load("wiki.zh.text.model")
 
In [3]: model.most_similar(u"足球")
Out[3]: 
[(u'\u8054\u8d5b', 0.6553816199302673),
 (u'\u7532\u7ea7', 0.6530429720878601),
 (u'\u7bee\u7403', 0.5967546701431274),
 (u'\u4ff1\u4e50\u90e8', 0.5872289538383484),
 (u'\u4e59\u7ea7', 0.5840631723403931),
 (u'\u8db3\u7403\u961f', 0.5560152530670166),
 (u'\u4e9a\u8db3\u8054', 0.5308005809783936),
 (u'allsvenskan', 0.5249762535095215),
 (u'\u4ee3\u8868\u961f', 0.5214947462081909),
 (u'\u7532\u7ec4', 0.5177896022796631)]
 
In [4]: result = model.most_similar(u"足球")
 
In [5]: for e in result:
    print e[0], e[1]
   ....:     
联赛 0.65538161993
甲级 0.653042972088
篮球 0.596754670143
俱乐部 0.587228953838
乙级 0.58406317234
足球队 0.556015253067
亚足联 0.530800580978
allsvenskan 0.52497625351
代表队 0.521494746208
甲组 0.51778960228

搞完这一波,一天也就差不多过去了……至于训练效果,取决于语料库以及我们的分词效果两点,可以针对这两点进行处理。

扩展阅读:

深度学习word2vec笔记之基础篇

Deep Learning实战之word2vec

Node.js 用Mocha+Chai做单元测试 入门

$
0
0

昨天是六一儿童节(发布的时候已经是前天了= =),给自己放了一天假,然后晚上开始看自动测试的问题。

单元测试是每个程序员都应该自测的部分(《构建之法》中说:单元测试应该由最熟悉程序的人来写——也就是些这段代码的程序员)传统的测试机械化程度太高,肉眼看也是累得不行,此外,代码覆盖率是一个很重要的考察点,人工测试在计算上或称最大难题。

基本概念

当然在此之前,先来科普一些基本概念,也就是单元测试的分类:TDDBDD

TDD

TDD(Test-Driven Development),也就是测试驱动开发,这意味着开发者先写测试样例,然后根据测试样例去实现它,当实现完成之后,他会发现所有的测试样例都是通过了的。

流程图如下:

TTD_flow.png

BDD

BDD(Behavior-Driven Development),行为驱动测试,他看着和TDD很像,但是和TDD又有所不同(当然写出来的测试代码本身其实是一样的)。

它意味着我们先进行代码的编写,然后根据我们代码所要达到的效果或者描述的事实去撰写测试代码,测试是否如我们预想的那样工作。

TDD 与 BDD的区别

在程序上,他们几乎没有区别,唯一的区别可能是修饰性描述,TDD强调将会实现的效果,可能是一句陈述句如:1+1等于2,而BDD强调结果是否正确,是否合乎预期,这句描述可能就会变成:1+1是否等于2。

当然,由于单元测试是程序员的自测流程,也谈不上哪个更好或者更差,选择自己喜欢的就可以了,在我看来BDD可能更符合我的口味,TDD并不一定能覆盖所有情况(因为我不一定考虑完全),这个时候之后根据实现慢慢调整我的测试样例可能更适合我。

另一方面,TDD有点像一份设计文档,而BDD像是之后的公开使用说明书。

如果你还想了解更多,可以见The Difference Between TDD and BDD

Mocha

了解完了基本概念,接下我们来使用工具。

Mocha是一个基于node.js和浏览器的集合各种特性的Javascript测试框架,并且可以让异步测试也变的简单和有趣。Mocha的测试是连续的,在正确的测试条件中遇到未捕获的异常时,会给出灵活且准确的报告。

Mocha是一个框架,具体还得靠断言库,断言库我们之后再介绍。

官方地址:http://mochajs.org/#getting-started

按照我的学习习惯,在使用前先跑一下Demo,扫一下官方的手册功能大致有哪些,方便日后查阅。

在此我们用官方样例为例(官方使用全局安装):

npm install -g mocha
mkdir test
$EDITOR test/test.js

之后我们使用后文即将提到的Chai库(这里另外也可以看出,describe是可以嵌套的):

var assert = require('chai').assert;
describe('Array', function() {
  describe('#indexOf()', function () {
    it('should return -1 when the value is not present', function () {
      assert.equal(-1, [1,2,3].indexOf(5));
      assert.equal(-1, [1,2,3].indexOf(0));
    });
  });
});

然后在Terminal运行mocha,可以看到运行结果(当然由于这里我们没有安装chai的库,所以无法顺利执行,之后我们就来说说Chai。

Chai

Chai是一个开源断言库,前端或者后端均可使用,因此学习Mocha+Chai这对组合还是很划得来的。

前端测试方式我们之后再说,先从简单的Node.js下手。

Chai官方网址:http://chaijs.com

npm install chai

在执行完毕之后,上文的样例就可以运行了,我们也能看到正确的结果,如果错误,就会抛出断言异常。

Chai提供多种语句,Expect/Should是BDD风格,而Assert是TDD风格,这个主要是风格有些语法差异,使用Should有些额外的注意事项,所以昨天后来我使用了BDD风格的Expect

Expect的风格可以看下官方文档:

var expect = require('chai').expect
  , foo = 'bar'
  , beverages = { tea: [ 'chai', 'matcha', 'oolong' ] };

expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.length(3);
expect(beverages).to.have.property('tea').with.length(3);

var answer = 43;

// AssertionError: expected 43 to equal 42.
expect(answer).to.equal(42);

// AssertionError: topic [answer]: expected 43 to equal 42.
expect(answer, 'topic [answer]').to.equal(42);

我们可以看到结构是expect(待测试数据).to.action(断言值)

具体的action可见:http://chaijs.com/api/bdd/

之后根据具体测试情况选择需要的测试Chain就行了。

代码覆盖报告

衡量单元测试,有一些很重要的参数,我们当然不至于去人工计算,istanbul库会帮我们做到这一点。

官方地址:https://github.com/gotwarlost/istanbul

在github也有相关介绍,这里简单说一下,执行的方法就是:istanbul cover _mocha,然后会执行mocha并且生成报告。

浏览器测试

mocha支持前端测试,结合chai即可使用。

mocha init

接下来引入chai的js文件之后再下面添加上相应测试用例的代码即可(使用方法和前文一致)

之后访问即可。

当然如果使用phantomjs的话效果会更好,这里看这篇文章就差不多了:

《浏览器端测试:mocha,chai,phantomjs》

需要发HTTP请求的测试情况

在测试网站时会有这个需求,这个时候superagent的姐妹supertest可以满足我们,还没有使用过,不过大致可以一看:

《测试用例:supertest》

参考链接

Nodejs单元测试小结

《测试用例:mocha,should,istanbul》

使用Mosh来改善你的SSH连接

$
0
0

今天看到一篇安利iTerm2的文章,在文章介绍了mosh,支持断续连接,当时就很心动,果断进官网看看详情:

Mosh:https://mosh.mit.edu/

mosh使用UDP(ssh使用的是tcp),能够在网络环境差的情况下也保持稳定和基本的使用(在我使用之后觉得流畅了许多,终于有心情折腾了)。

安装方法相当简单,跨平台性也相当好(除了iPhone和Windows Phone):

先在OSX中安装命令:brew install mobile-shell

之后我们就能够使用mosh usr@host代替ssh usr@host,当然在服务端中也需要安装mosh。

我用的是CentOS,直接yum install mosh,之后mosh-server,可以看到:

MOSH CONNECT 60001 

运行netstat -anp | grep mosh查看一下mosh对外的端口,我这里是60002,这是为了设置iptables(如果开启防火墙的话),如果没有开启防火墙,现在已经可以顺利的连上了,mosh会读取ssh的配置并根据ssh的配置来运行,属于可以完美替代的类型。

如果你开启了iptables,记得在允许通过的规则列表中添加:

-A INPUT -p udp -m udp --dport 60002  -j ACCEPT

之后service iptables restart即可,甚至可以愉快的关闭22端口的对外访问(但是并不是关闭ssh的意思)。

补充:发现关闭22端口之后如果重启电脑重新使用mosh usr@server命令是无法进入的,需要打开22端口才可连入,但连入后可关闭22端口,所以对于22端口的关闭请谨慎操作。

SSH实现免密码登录远程服务器并且关闭密码登录功能

$
0
0

标题好长……这次被黑之后吸取了教训,大致说一下做的事情:

首先能不用root就不用root,除了由于普通用户拿不到权限的部分以外都使用普通用户(如Node.js程序的运行),不关闭SELINUX,开启iptables(这里需要说一下,只要开启了防火墙就会生成iptables),未开启则没有,所以才会出现找不到iptables的情况。

免密码具体就是用证书(非对称加密)来登录,处理起来其实也是蛮方便的(但如果一个手滑可能会导致登录不上去)。

如果过去没有生成过证书,则需要先运行 ssh-keygen -t rsa,之后再~/.ssh/目录会生成公钥和私钥,如果运行过了,就不要重复运行了。

之后把公钥(xxx.pub),传到服务器中的~/.ssh/目录下,重命名为authorized_keys,如果已经有该文件,则把公钥内容加入文末。

之后设置authorized_keys权限为600(必须严格设置好,否则风险极大)。

之后开启公钥登录的方法,在/etc/ssh/sshd_config找到PubkeyAuthentication,取消注释并改为PubkeyAuthentication yes

保存并验证(需要重启service sshd restart)。

之后我们在客户端用ssh user@ip登录,如果可以,那么接下来就能关闭密码登录了,再次进入配置文件,找到PasswordAuthentication改为no,ChallengeResponseAuthentication改为no。

再次保存并重启,之后就无法用密码登录了,不过,密码登录的修改是针对全局,而证书是针对单用户,如果需要多用户都配置证书,重复加入authorized_keys即可。

需要注意的是整个操作如果你没有VNC或者自动恢复的脚本还是比较危险的(可能会导致无法登录),所以谨慎操作。

Viewing all 195 articles
Browse latest View live




Latest Images