About
RSS

Bit Focus


Redis Cluster 简单配置与动态扩容

    Redis 3.0 就要自带集群功能了, 去看了一下这里还有官方教程之后, 发现似乎必须用命令行来搞着, 而且官方提供的 redis-trib.rb 要求至少 3 个节点才能建立一个集群, 这规格是向党支部看齐么...
    至少 3 个节点这个还是略坑, 而且不能自动添加节点 (难道要我启动个 py 的 subprocess 去掉 ruby?), 于是去看看源代码, 惊讶地发现, 原来限制 3 个节点起步的是 ruby 脚本, 而且调集群加节点平衡负载其实都可以用 redis 命令来完成. 好吧, 那我自己来连 socket 搞总行了吧.
    结果一番折腾还真的可行的样子, 于是有了这篇文章和一个简单的工具. 那么首先说说怎么用 redis-cli 来做这些事情.

    如何在 redis-cli 手动启动集群呢, 请随意连上一个空的支持集群模式的节点, 然后执行
cluster addslots 0 1 2 ... 16383
    千万不要误会了, 中间那个 ... 可是要实打实地从头写到尾的哦. 所以如果可以的话, 手动写个脚本来干这事情吧.
    不过也可以略过这些步骤, 反正下面看看例子就行, 最后会给出一个 Python 工具来做这些.
    接下来的例子中, 假定已经开好了一个集群, 共有 3 个 master 节点. 要在控制台检视这些节点, 请用 redis-cli 随意连上其中一个, 并执行
cluster nodes
    输出
e7f4fcc0dd003fc107333a4132a471ad306d5513 127.0.0.1:8001 master - 0 1414033928009 3 connected 0-2729 8192-10921
bd239f7dbeaba9541586a708484cdce0ca99aba5 127.0.0.1:8000 master - 0 1414033929011 2 connected 2730-8191
787e06e9d96e6a9a3d02c7f3ec14e243882293e9 127.0.0.1:7999 myself,master - 0 0 1 connected 10922-16383
以上每一行是一个节点信息, 按空格分隔的域依次表示
  • 节点 ID
  • 节点地址
  • 节点角色 (master / slave), 如果是当前节点, 还会有个 myself
  • 对于 slave 而言, 其 master 节点的 ID
  • 最后一次 ping 时间戳
  • 最后一次 pong 时间戳
  • 节点顺序号
  • 节点连接状态
  • 之后的所有 : 节点所配给的槽位, 如果槽位连续, 就以 BEGIN-END 表示, 不连续的由空格隔开
    如果要向集群新增一个节点, 需要用 redis-cli 连上这个新节点, 调用一次 cluster meet 命令. 如
cluster meet 127.0.0.1 7999
后面参数是已经在集群中的节点中任意一个节点的地址及端口. 然后再来一次

Permanent Link: ?p=524 Load full text

Post tags:

 Python
 Redis
 集群

如何弄一个在不同站点做不同事情的 Chromium 扩展

    先解释一下为什么有这个需求.
    国内似乎有不少所谓的说好听叫资源聚合网站说直白叫盗文章的网站, 虽然鄙博客文章质量很一般, 但也至少被三个不同的网站全文抓取了 (http://outofmemory.cn/ http://www.taocms.org/ http://www.tuicool.com/). 其实流量点击量什么的都不是个事, 我也没打算靠写博客赚钱, 问题是这些网站长得都太残了. (tuicool 还好一点, outofmemory 代码都没用等宽字体你那网站能看! 简直白白浪费这么好个域名) 于是就有了这么个需求: 当访问到这些网站时自动跳转到原博客页面.
    当然了各位读者不必搞这么过河拆桥的需求, 大可写个插件去展开豆瓣页面上的那些短网址什么的.

    简单看一下 Chromium 扩展的结构, 无非就是一个配置文件 (manifest.json) 加上一些 JS 文件, 有必要的话再加上一些 HTML 文件. 这里就说说最简单的, 进入一个网站在页面加载完毕之后执行一个指定 JS 文件中的代码. 那么配置文件要这么写
{
  "name": "ExtensionName",
  "version": "0.1.0",
  "description": "ext descr",
  "browser_action": {
    "default_title": "Extension Title"
  },
  "content_scripts": [
    {
      "matches": ["http://ju.outofmemory.cn/entry/*"],
      "js": ["outofmemory.js"]
    }
  ],
  "manifest_version": 2
}
    以上 JSON 中, content_scripts 部分是个数组, 其中每个元素有至少两个属性, matches 表示在 URL 满足什么条件时加载脚本, 而 js 则是加载那些脚本; 如果扩展要用到 jQuery 之类的库, 可以加到此数组最前面.
    也就是说, 上面这个例子会在所有 URL 开头为 http://ju.outofmemory.cn/entry/ 的网页上, 调用 outofmemory.js 这个文件. 而这个文件的内容很简单
-function() {
    console.log('Hello world!');
    // window.location = document.getElementsByClassName('copyright')[0].getElementsByTagName('a')[1].href;
}()
    想了一下我还是把代码藏着点, 仍然用业界标准的 Hello world 来开场好了.
    新建一个目录, 把这两个文件保存在此目录下.
    点 Chromium 浏览器菜单 -> 工具 (Tools) -> 扩展程序 (Extensions), 勾选开发者模式 (Developer mode) 下面会刷出来 3 个按钮, 最左边就是加载野生扩展, 在对话框中选中刚才新建的目录, 这样扩展就上线了. 然后挑个页面进去看看控制台吧, 比如 http://ju.outofmemory.cn/entry/81081
    到此代码能执行了, 后面就没什么需要继续说的了, 剩下就是自己抓 DOM 看该怎么搞怎么搞吧.

    附项目地址

Permanent Link: ?p=523 Load full text

Post tags:

 Javascript
 Chromium 扩展

麻将听牌算法 [下篇]

上篇中分析了听牌可能有关字牌的情形, 具体包括字牌中有一个单张, 而剩下的数牌全能构成面子的单骑醒, 或者字牌中有个对子, 而剩下某数牌含有一个对子的双碰型或一个搭子的边/嵌张听牌. 这篇要讨论字牌全是刻子时的类似情况. 之所以说类似是由于此时数牌只可能有以下两种情况
  • 某一色数牌的牌总数模 3 余 1, 其它两个色都能恰好构成面子
  • 某两色数牌的牌总数摸 3 余 2, 剩下一色能恰好构成面子
体现成代码就是, 需要解决以下两个函数
def _waits_4groups(tiles):
    # 前略
    # 在前面情况不满足时, 调用如下实现
    return (_detect_numeric_suit_with_one_more(tiles) +
            _detect_2_numeric_suits_with_2_more(tiles))

# 找一个花色, 它的数量模 3 余 1
def _detect_numeric_suit_with_one_more(tiles):
    pass

# 找两个花色, 它们各自的牌的数量模 3 都余 2
def _detect_2_numeric_suits_with_2_more(tiles):
    pass
在上一篇代码的支援下, 后一个函数的实现相对容易一些, 如下

Permanent Link: ?p=522 Load full text

Post tags:

 Algorithm
 麻将
 Python

麻将听牌算法 [上篇]

    作为一个人类经常在打清一色的时候望着手牌不知道听牌没不知道听了哪几张也不知道切哪一张会让听牌数量最大化是一件不愉快的事情, 除了九莲宝灯之类的定式役给背下来好像没别的有效方法. 或者, 写个程序来搞吧.
    首先是数据结构, 这里用如下类来描述

Permanent Link: ?p=521 Load full text

Post tags:

 麻将
 Algorithm
 Python

数学证明的边界扩张

    最近看着证明与反驳这本书, 虽然不是什么数学著作, 讨论的也不是改变世界的重要定理, 不过围绕着多面体面棱角个数关系的那个欧拉定理讲得风生水起, 还是挺有意思的.
    书里提到数学研究的一个策略, 对于一个已经证明的定理, 通过想方设法扩张其证明步骤中的条件「边界」, 也许可以把定理从一个特殊形式扩展到普遍形式. 就像写程序库一样, 总希望写出来的东西能尽可能满足各种不同参数下的需求, 虽然在软件行业这么干往往会把自己玩死...
    比如看看费马平方和定理第四步的结论 (请一定看完前四步证明, 后文的内容均是修改此证明)
  • 对于互素的任意整数 a 和 b, a2 + b2 的每一个因子也都能表示为两个整数的平方和的形式
    这一步虽然内容上有不少平方和, 不过还不是平方和定理实际内容, 只是一步中间引理. 然而即使如此证明看起来也挺费神的.
    要扩展这个定理的话, 可以从多个不同角度出发, 对于两个整数平方和成立, 对于三个整数平方和是否成立; 或者, 本文将采取的方式如下
  • 给定正整数 v, u, 对于互素的任意整数 a 和 b, v * a2 + u * b2 的每一个因子也都能表示为 v * p2 + u * q2 的形式
    然后试着往原来的证明过程中套, 看看会发生什么.

    第一步用到了名字巨长的恒等式, 这个等式说两个平方和的乘积还是一个平方和, 在代数学中有个术语叫封闭性. 比如整数集里面任取两个元素出来进行加减乘运算, 结果还是整数 (除就不一定了), 那么加减乘这三种运算 (三个二元函数) 对于整数集就是封闭的. 类似的, 上述恒等式说明在所有能表示成两个整数平方和的数的集合内, 乘法是封闭的.
    那么这个结论能否运用到形如 v * a2 + u * b2 的整数呢? 很快就能验证
(v * a2 + u * b2) * (v * c2 + u * d2)
  = v2 * (ac)2 + vu * ((ad)2 + (bc)2) + u2 * (bd)2
  = v2 * (ac)2 + u2 * (bd)2 + 2 * uv * abcd
    + vu * ((ad)2 + (bc)2- 2 * uv * abcd
  = (vac + ubd)2 + vu(ad - bc)2
    并不是 (v * p2 + u * q2) 而是 (p2 + vu * q2) 的形式. 真糟糕, 第一步封闭性就阵亡了. 那么今天的内容就到这里, 谢谢大家观看, 我们下期节目再见...
    噢等等, 我觉得这个扩张还可以抢救一下, 只要作出一点小牺牲, 把内容收缩一点就好: 令 v, u 之一等于 1, 也就是说变成比如

Permanent Link: ?p=520 Load full text

Post tags:

 数学

VerbalExpressions 与状态机词法分析器

VerbalExpressions

    说到字符串检索分析替换修改自然会想到正则表达式, 不过这东西实在是一个只写语言. 更改系统中一个一般复杂的正则表达式, 传统的读懂代码然后替换一条语句或者加上一个分支或者参数的模式不管用, 而是直接重写, 就像清理一个塞满的垃圾桶, 方法不是把垃圾一点点挖出来, 而是整个倒掉再铺上新的垃圾袋; 正则表达式有时太复杂了, 一条语句一个调用就顶过一打的循环和分支.
    人们总会想到一些更节省脑细胞的方式来对付字符串, 让机器理解人类的咒语, 于是发明了 VerbalExpressions.
    下面是一个 JS 的例子
var tester = VerEx()
            .startOfLine()
            .then("http")
            .maybe("s")
            .then("://")
            .maybe("www.")
            .anythingBut(" ")
            .endOfLine();
    上面这一串等价于 /^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$/ 这么个正则表达式, 不过书写起来显得科学多了; 如果需要更改逻辑, 也很容易下手到底是什么地方需要增加或者减少一点什么.

基于自动机的词法分析器

    这个轮子很有启发性, 于是乎想到以类似的方式构造个词法解析器. 接口上的愿景是类似
var t = Tokenizer();
t.simpleSymbols('+-*/=', 'operator')
 // ...
 .ignore(' ')
 .ignore('\t')
 .ignore('\r')
 // ...
 // 上面都是单独的一个字符, 接下来是循环的模式
 .loop(DIGITS)
 .accept('integer') // 以 0-9 循环的模式, 接受为整数类型

 .startWith(LETTERS)
 .loop(LETTERS + DIGITS)
 .accept('identifier') // 以字母开头, 数字和字母循环的模式, 接受为标识符

 // 接下来是保留字
 .fixed('if')
 .fixed('for')
 // 以及一些超过 1 字符的操作符
 .fixed('==', 'operator')
 .fixed('<=', 'operator')
 // ...
;
var inputString = 'for (i = 0; i < 10; i = i + 1) { if (i % 3 == 0) { print(i); } }';
var tokenArray = t.tokenize(inputString);
console.log(tokenArray);
    看起来应该是这么回事. 以这种方式构造出来的东西应该是一个状态机而不是一大波正则表达式形成的集群. 因此首先得构造一个状态数据结构. 作为一个演示就不弄太复杂了, 它看起来类似

Permanent Link: ?p=519 Load full text

Post tags:

 Javascript
 VerbalExpressions
 Compiler Construction

就算是 Linux 命令行只要有爱就能剪辑 MAD 了吧

已经转移到 http://zlo.gs/p/neuront/linux-dakedo-ai-sae-areba-mad-dekiru-yone

Permanent Link: ?p=518 Load full text

Post tags:

 Python
 视频剪辑

索引统计与 Python 字典

    最近折腾索引引擎以及数据统计方面的工作比较多, 与 Python 字典频繁打交道, 至此整理一份此方面 API 的用法与坑法备案.

    索引引擎的基本工作原理便是倒排索引, 即将一个文档所包含的文字反过来映射至文档; 这方面算法并没有太多花样可言, 为了增加效率, 索引数据尽可往内存里面搬, 此法可效王献之习书法之势, 只要把十八台机器内存全部塞满, 那么基本也就功成名就了. 而基本思路举个简单例子, 现在有以下文档 (分词已经完成) 以及其包含的关键词
  • doc_a: [word_w, word_x, word_y]
  • doc_b: [word_x, word_z]
  • doc_c: [word_y]
将其变换为
  • word_w -> [doc_a]
  • word_x -> [doc_a, doc_b]
  • word_y -> [doc_a, doc_c]
  • word_z -> [doc_b]
    写成 Python 代码, 便是
doc_a = {'id': 'a', 'words': ['word_w', 'word_x', 'word_y']}
doc_b = {'id': 'b', 'words': ['word_x', 'word_z']}
doc_c = {'id': 'c', 'words': ['word_y']}

docs = [doc_a, doc_b, doc_c]
indices = dict()

for doc in docs:
    for word in doc['words']:
        if word not in indices:
            indices[word] = []
        indices[word].append(doc['id'])

print indices
    不过这里有个小技巧, 就是对于判断当前词是否已经在索引字典里的分支
if word not in indices:
    indices[word] = []
可以被 dictsetdefault(key, default=None) 接口替换. 此接口的作用是, 如果 key 在字典里, 那么好说, 拿出对应的值来; 否则, 新建此 key, 且设置默认对应值为 default. 但从设计上来说, 我不明白为何 default 有个默认值 None, 看起来并无多大意义, 如果确要使用此接口, 大体都会自带默认值吧, 如下
for doc in docs:
    for word in doc['words']:
        indices.setdefault(word, []).append(doc['id'])
    这样就省掉分支了, 代码看起来少很多.
    不过在某些情况下, setdefault 用起来并不顺手: 当 default 值构造很复杂时, 或产生 default 值有副作用时, 以及一个之后会说到的情况; 前两种情况一言以蔽之, 就是 setdefault 不适用于 default 需要惰性求值的场景. 换言之, 为了兼顾这种需求, setdefault 可能会设计成
def setdefault(self, key, default_factory):
    if key not in self:
        self[key] = default_factory()
    return self[key]
倘若真如此, 那么上面的代码应改成
for doc in docs:
    for word in doc['words']:
        indices.setdefault(word, list).append(doc['id'])

Permanent Link: ?p=517 Load full text

Post tags:

 Data Structure
 Python

Page 0 1 2 3 4 5 6 7 8 9 10 11 12


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site