基于MMSeg算法的中文分词类库

基于MMSeg算法的中文分词类库

最近在实现基于lucene.net的搜索方案,涉及中文分词,找了很多,最终选择了MMSeg4j,但MMSeg4j只有Java版,在博客园上找到了*王员外*(http://www.cnblogs.com/land/archive/2011/07/19/mmseg4j.html )基于Java版的翻译代码,但它不支持最新的Lucene.Net 3.0.3,于是基于它的代码升级升级到了最新版Lucene.Net (≥ 3.0.3),同时将其中大部分Java风格代码修改为.Net风格,并修正了其中几个小错误。

为了方便大家使用,我把修改后代码放到Github上了,并包含简单示例代码。另外,为了方便使用,制作了NuGet安装包,上传到了NuGet上,使用时,直接NuGet搜索Lucene.Net.Analysis.MMSeg即可。

Git地址

https://github.com/JimLiu/Lucene.Net.Analysis.MMSeg

NuGet地址

https://nuget.org/packages/Lucene.Net.Analysis.MMSeg/

PM> Install-Package Lucene.Net.Analysis.MMSeg

使用

一共三种搜索模式供选择:

SimpleAnalyzer

Analyzer analyzer = new SimpleAnalyzer();

MaxWordAnalyzer

Analyzer analyzer = new MaxWordAnalyzer();

ComplexAnalyzer

Analyzer analyzer = new ComplexAnalyzer();

具体使用方法,请参考代码中的示例和lucene.net的文档

Scala中的语言特性是如何实现的(1)

2013-05-09 22:38 by 崔鹏飞, 55 阅读, 0 评论, 收藏编辑

Scala可以编译为Java bytecode和CIL,从而在JVM和CLI之上运行。Scala有很多在Java和C#的世界中显得陌生的语言特性,本文将分析这些语言特性是如何实现的。

object

Scala中可以像这样创建object:

1
2
3
4
5
object HowIsObjectImplementedInScala {  def printSomething() {  println("printSomething")  } } 

然后在代码的其他地方调用printSomething,一个object究竟是什么东西呢? 我们将这段Scala编译为Java bytecode,然后反编译为Java,会发现编译器为HowIsObjectImplementedInScala这个object生成了两个类:

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
public final class HowIsObjectImplementedInScala {  public static void printSomething()  {  HowIsObjectImplementedInScala..MODULE$.printSomething();  } } public final class HowIsObjectImplementedInScala$ {  public static final MODULE$;  static  {  new ();  }  public void printSomething()  {  Predef..MODULE$.println("printSomething");  }  private HowIsObjectImplementedInScala$()  {  MODULE$ = this;  } } 

第一个类只包含一个静态方法,其实现依赖于第二个叫做HowIsObjectImplementedInScala$的类。

HowIsObjectImplementedInScala$是一个单例,其静态块实例化自己并把this赋值给MODULE$这个public static的成员,从而可以被外界访问。

同样,我们可以把这段代码编译为CIL,然后反编译为C#:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public sealed class HowIsObjectImplementedInScala {  public static void printSomething()  {  HowIsObjectImplementedInScala$.MODULE$.printSomething();  } } public sealed class HowIsObjectImplementedInScala$ : ScalaObject {  public static HowIsObjectImplementedInScala$ MODULE$;  public override void printSomething()  {  Predef$.MODULE$.println("printSomething");  }  private HowIsObjectImplementedInScala$()  {  HowIsObjectImplementedInScala$.MODULE$ = this;  }  static HowIsObjectImplementedInScala$()  {  new HowIsObjectImplementedInScala$();  } } 

和Java代码大同小异,除了静态构造和某几个关键字外,基本一样。一个object就是一个Scala编译器帮我们实现的singleton。

var和val

var:可变。val:不可变。关于这两个关键字何时该使用哪一个,这里不做讨论,我们只是观察这二者在编译后是如何被表示的。

这段Scala代码:

1
2
3
4
5
6
7
8
9
10
class HowAreVarAndValImplementedInScala {  var v1 = 123  val v2 = 456  def method1() = {  var v3 = 123  val v4 = 456  println(v3 + v4)  } } 

定义了两个字段一个var,一个val,方法中定义了两个局部变量,一个var,一个val。

编译为Java bytecode并反编译之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class HowAreVarAndValImplementedInScala {  private int v1 = 123;  private final int v2 = 456;  public int v1()  {  return this.v1;  }  public void v1_$eq(int x$1) { this.v1 = x$1; }  public int v2() { return this.v2; }  public void method1() {  int v3 = 123;  int v4 = 456;  Predef..MODULE$.println(BoxesRunTime.boxToInteger(v3 + v4));  } } 

声明为字段的v1和v2,一个是普通字段,另一个则被标记为final。编译器为v1生成了getter和setter,为v2则只有getter,因为v2作为immutable的字段是不可以被重新赋值的。

有趣的是方法中的局部变量都是普通的变量,没有被final修饰。

再来看这段Scala编译为CIL再反编译为C#之后的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class HowAreVarAndValImplementedInScala : ScalaObject {  private int v1;  private int v2;  public override int v1()  {  return this.v1;  }  public override void v1_$eq(int x$1)  {  this.v1 = x$1;  }  public override int v2()  {  return this.v2;  }  public override void method1()  {  int v3 = 123;  int v4 = 456;  Predef$.MODULE$.println(v3 + v4);  }  public HowAreVarAndValImplementedInScala()  {  this.v1 = 123;  this.v2 = 456;  } } 

有一个明显的问题,v2没有标为readonly(C#世界中用于声明变量不可以重新赋值的关键字),这是compiler的bug吗?

除此之外,和Java代码一致。但是有趣的是代码中的所有public方法(包括上一段演示object的代码)都被标为了override,原因不明。

小结

本来以为研究这么简单的两个语言特性不会有啥收获,仅仅是反编译一下,看看compiler都做了啥,满足下好奇心罢了。

结果还是有意外收获,我在反编译后的代码中发现了三个有趣的问题:

  • 在Scala中被声明为val的v4为什么在反编译的Java中不是final的呢?
  • 在Scala中被声明为val的v2为什么在反编译的C#中不是readonly的呢?
  • 为什么反编译出来的C#代码中的实例级公开方法都是标有override的呢?

为什么呢?为什么呢?为什么呢?答案下期揭晓。

如何一步一步推导出Y Combinator

2013-04-13 22:24 by 崔鹏飞, 585 阅读, 1 评论, 收藏编辑

粘贴过来的代码高亮有问题,可以到我的另一个博客阅读:http://cuipengfei.me/blog/2013/04/09/make-y/

 

本文讲什么?

本文用Scheme(Racket)代码为例,一步一步的推出Y Combinator的实现。

本文不讲什么?

Y Combinator是什么,干什么用的,它为什么能够work,它的数学含义以及实际应用场景,这些话题由于篇幅所限(咳咳,楼主的无知)不在本文论述范围之内。

如果有兴趣,请参考维基: http://en.wikipedia.org/wiki/Fixed-point_combinator#Y_combinator

鸣谢

感谢Jojo同学的 这段JavaScript代码的启发,我写了对应的Scheme实现。然后才有了本文。

正文开始

我们知道Y Combinator可以帮匿名函数实现递归。那就从一个广为人知的递归函数-阶乘开始吧。

1
2
3
(define (fac1 n)  (if (< n 2) 1  (* n (fac1 (- n 1))))) 

如果n小于2,则返回1,否则开始递归,简单明了。如果像这样调用它一下:

1
(fac1 5) 

会返回120,结果无误。

上面是阶乘的递归实现,它有一个名字叫做fac1,但是如果用匿名函数实现阶乘呢?

1
2
3
4
(lambda (f)  (lambda (n)  (if (< n 2) 1  (* n (f (- n 1)))))) 

这个匿名函数“梦想着”其调用者会把该函数自己的实现作为参数传递进去。

1
2
3
4
5
6
7
8
(((lambda (f)  (lambda (n)  (if (< n 2) 1  (* n (f (- n 1))))))  (lambda (f)  (lambda (n)  (if (< n 2) 1  (* n (f (- n 1))))))) 1) 

我们把匿名函数重复写一遍,就可以计算1或者是0的阶乘,但是要计算3的阶乘呢?那就得这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(((lambda (f)  (lambda (n)  (if (< n 2) 1  (* n (f (- n 1))))))  ((lambda (f)  (lambda (n)  (if (< n 2) 1  (* n (f (- n 1))))))  ((lambda (f)  (lambda (n)  (if (< n 2) 1  (* n (f (- n 1))))))  (lambda (f)  (lambda (n)  (if (< n 2) 1  (* n (f (- n 1))))))))) 3) 

想要计算一个大于2的n的阶乘,就得把这个匿名函数重复写n+1次。这么多的重复代码,这么多的括号。。。

所以我们需要一个神奇的函数,Y,它可以接受一个匿名的伪递归函数作为参数,产出一个真递归的函数。 这个神奇的Y作用在上面的匿名函数上之后产出的结果就可以用来计算任何n的阶乘。下面的代码会输出120(如果Y已经实现了的话)。

1
2
3
4
((Y (lambda (f)  (lambda (n)  (if (< n 2) 1  (* n (f (- n 1))))))) 5) 

下面就开始一步步的构造这个神奇的Y吧。

为了便于推导,暂时给这个匿名函数一个名字,叫做fake_fac。

1
2
3
4
5
(define fake_fac  (lambda (f)  (lambda (n)  (if (< n 2) 1  (* n (f (- n 1))))))) 

有了这个名字之后,再要计算3的阶乘就容易了一些。

1
((fake_fac (fake_fac (fake_fac fake_fac))) 3) 

观察上面的代码,我们把fake_fac传递给它自己,得到一个返回值,把这个返回的值再次传递给fake_fac,再得到一个新的返回值,又把新的返回值传递给fake_fac,得到最终的返回值,最后把3传递给这个返回值。

可以看到,我们在不停的把fake_rec传给它自己,所以定义一个helper吧:

1
(define (callself f) (f f)) 

这个helper一会儿会派上用场。

现在看看fake_fac中的f是什么呢?对于((fake_fac (fake_fac (fake_fac fake_fac))) 3)这行代码中的最右侧的fake_fac来说,f没有用,因为这个fake_fac自己都没有被调到,它只是起个占位符的作用,实际上这行代码((fake_fac (fake_fac (fake_fac 1))) 3)和上面的那行是等价的。

对于右侧第二个fake_fac来说,f就是fake_fac。对于左侧第二个fake_fac来说,f是(fake_fac fake_fac)的返回值。

对于左侧第一个fake_fac来说,f是(fake_fac (fake_fac fake_fac))的返回值。

由此可见,f是fake_fac对自己反复调用的返回值。而且从fake_fac的定义可见,我们总是给f传递一个数字n,这样的话,我们再写一个helper:

1
(lambda (n) ((f f) n)) 

再把这个helper传递给fake_fac。

1
(fake_fac (lambda (n) ((f f) n))) 

但是上面这两段代码是有问题的,因为f的值无法确定。

有句话说的好: if you don’t know exactly what you want to put somewhere in a piece of code, just abstract it out and make it a parameter of a function. 所以我们就把f抽成参数吧。

1
2
(define (callselfWithN f)  (fake_fac (lambda (n) ((f f) n)))) 

我们希望这个helper可以帮fake_fac无限次的调用自己。

现在,我们该怎么调用callselfWithN呢?不能把fake_fac传给它,因为那样的话(f f)就只是fake_fac对自己的调用,它只能计算0或者1的阶乘。所以要把callselfWithN这个我们希望可以帮fake_fac实现无限次自调用的函数传给callselfWithN它自己。

1
((callselfWithN callselfWithN) 5) 

这行代码可以返回120,结果正确了!

记得前面定义的第一个helper吗?现在用的上了:

1
((callself callselfWithN) 5) 

现在把callselfWithN带入:

1
2
((callself (lambda (f)  (fake_fac (lambda (n) ((f f) n))))) 5) 

可以看出,这段代码和fake_fac是紧耦合的,把它抽到参数上去:

1
2
3
(define (Y3 fake_recur)  (callself (lambda (f)  (fake_recur (lambda (n) ((f f) n)))))) 

然后再把callself也带入:

1
2
3
4
5
(define Y (lambda (fake_recur)  ((lambda (f) (f f))  (lambda (f)  (fake_recur  (lambda (n) ((f f) n))))))) 

现在Y不依赖于任何其他函数了,测试一下Y,把前面的计算阶乘的匿名函数传给它:

1
2
3
4
((Y (lambda (f)  (lambda (n)  (if (< n 2) 1  (* n (f (- n 1))))))) 5) 

能够返回120,正确!Y Combinator构造完成!

你可能感兴趣的:(中文分词)