【程序设计实践】第3章 设计与实现

来源:互联网 发布:红海军知乎 编辑:程序博客网 时间:2024/06/05 15:52
3章 设计与实现

数据结构设计是程序构造过程的中心环节。一旦数据结构安排好了,算法就像是瓜熟蒂落,编码也比较容易。

程序设计语言的选择在整个设计过程中,相对而言,并不是那么重要。程序的设计当然可以通过语言来装饰,但是通常不会为语言所左右。

马尔可夫链算法

把输入想像成由一些互相重叠的短语构成的序列,该算法把每个短语分成两部分:一部分由多个词构成的前缀,另一部分是只包含一个词的后缀。马尔可夫链算法能够生成输出短语的序列,其方法是依据(在我们的情况下)原文本的统计性质,随机性地选择跟在前缀后面的特定后缀。采用三个词的短语就能够工作得很好——利用连续两个词构成的前缀来选择作为后缀的一个词:

设置w1w2为文本的前两个词

输出w1w2

循环:

  随机地选出w3,它是文本中w1w2的后缀中的一个

  打印w3

  把w1w2分别换成w2w3

  重复循环 

选择二词前缀,则每个输出词w3都是根据它前面的一对词(w1,w2)得到的。前缀中词的个数对设计本身并没有影响,程序应该能对付任意的前缀长度。我们把一个前缀和它所有可能后缀的集合放在一起,称其为一个状态。

数据结构选择: 

对于后缀,我们需要在输出时随机选择一个,考虑用ListSet为容器(代码中用List)。对于前缀,我们需要快速查找,并每个前缀对应一系列的后缀,考虑用Map储存,因其可以产生<key,value>键值对。 

即: 

Map<Prefix,List<String>> stateTable = new HashMap<Prefix,List<String>>();

1.前缀以类Prefix表示,类Prefix有一个属性:List<String> pref; 前缀保存的两个词,w1,w2按顺序存入。 

Prefix有两个构造方法:Prefix(int npref, String word)用于把npref个的word复制到pref。 

Prefix(Prefix prefix)用于把prefix复制到当前实例。 

另外重写了PrefixhashCodeequals方法,以便于作为Mapkey。 

package chapter3;

import java.util.ArrayList;

import java.util.List;

/**

 * 保存前缀向量的词

 * @author bosshida

 *

 */

public class Prefix {

public List<String> pref;

//n copies of str

public Prefix(int npref, String word) {

pref = new ArrayList<String>();

for(int i=0; i<npref; i++){

pref.add(word);

}

}

// duplicate existing prefix

public Prefix(Prefix prefix) {

this.pref = new ArrayList<String>(prefix.pref);

}

private static final int MULTIPLIER = 31; //for hashcode()

public int hashCode(){

int h = 0;

for(int i=0; i<pref.size(); i++){

h = h*MULTIPLIER + pref.get(i).hashCode();

}

return h;

}

//compare two prefixes for equal words

public boolean equals(Object o){

Prefix p = (Prefix)o;

for(int i=0; i<pref.size(); i++){

if(!pref.get(i).equals(p.pref.get(i))){

return false;

}

}

return true;

}

}


2.Chain,用于读取输入、构造散列表并产生输出。 

类内有build(InputStream in)generate(int nwords)方法,build()用于从输入流产生状态表(stateTable,也就是散列表Map)generate()用于产生输出。 

package chapter3;

import java.io.InputStream;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import java.util.Random;

import java.util.Scanner;

/**

 * 读取输入,构造散列表,产生输出

 * @author bosshida

 *

 */

public class Chain {

static final int NPREF = 2; //size of prefix

static final String NOWORD = "\n"; //word that can't appear

Map<Prefix,List<String>> stateTable = new HashMap<Prefix,List<String>>();//key=prefix,value=suffix list;

Prefix prefix = new Prefix(NPREF,NOWORD);//initial prefix

Random random = new Random();

//chain build:build state table from input stream

public void build(InputStream in) throws Exception {

Scanner scanner = new Scanner(in);

while(scanner.hasNext()){

add(scanner.next());

}

add(NOWORD);

}

private void add(String word) {

List<String> suf = stateTable.get(prefix);

if(suf == null){

suf = new ArrayList<String>();

stateTable.put(new Prefix(prefix),suf);

}

suf.add(word);

prefix.pref.remove(0);

prefix.pref.add(word);

}

//chain generate: generate output words

public void generate(int nwords) {

prefix = new Prefix(NPREF,NOWORD);

for(int i=0; i<nwords; i++){

List<String> suf = stateTable.get(prefix);

int r = Math.abs(random.nextInt() % suf.size());

String word = suf.get(r);

if(word.equals(NOWORD)){

break;

}

System.out.print(word+" ");

prefix.pref.remove(0);

prefix.pref.add(word);

}

}

}


3.公共接口类Markovmain

package chapter3;

import java.io.File;

import java.io.FileInputStream;

/**

 * 马尔可夫算法

 * @author bosshida

 *

 */

public class Markov {

private static final int MAXGEN = 1000; //max words generated

public static void main(String[] args) throws Exception {

Chain chain = new Chain();

int nwords = MAXGEN;

FileInputStream fis = new FileInputStream(new File("e:/Book/alan.txt"));

chain.build(fis);

chain.generate(nwords);

}

}


以上程序在一本20万个英文单词的书为输入,产生1000字的输出。输出结果单句长度太长,而且语法很多错误,不过也是至少比随机性的输出有规律。 

对于后续的探究。 

本代码中,后缀以List保存,会有重复词的出现,重复词相当于这些词的权重加大,在输出的出现的机率增加,可能这是有益的。或者想各词出现的概率尽量一至,可考虑用Set保存后缀词。 

为产生的句子不过于太长,可在建立状态表stateTable时,增加有标点的单词的权重(保存的后缀词是带标点符号的)。为增加权重可采取方法有:(1)单词有标点时,可重复增加该单词,以增大该单词的出现概率。(2)可修改Map的结构,value改为用:Suffix类,包含String word, int weight。不过这样改随机取词的方法要修改。 


原创粉丝点击