精华内容
下载资源
问答
  • 1 Lucene简介Lucene是apache下的一个开源的全文检索引擎工具。1.1 全文检索(Full-text Search)1.1.1 定义全文检索就是先分词创建索引,再执行搜索的过程。分词:就是将一段文字分成一个个单词全文检索就将一段文字...

    1 Lucene简介

    Lucene是apache下的一个开源的全文检索引擎工具包。

    1.1 全文检索(Full-text Search)

    1.1.1 定义

    全文检索就是先分词创建索引,再执行搜索的过程。

    分词:就是将一段文字分成一个个单词

    全文检索就将一段文字分成一个个单词去查询数据!!!

    1.1.2 应用场景

    1.1.2.1 搜索引擎(了解)

    搜索引擎是一个基于全文检索、能独立运行、提供搜索服务的软件系统。

    ca2b2f5149640ade70a5fe2d549783e5.png

    1.2 Lucene实现全文检索的流程

    83080e7fcc0b3a5f4f9edba4b524f30a.png

    全文检索的流程分为两大部分:索引流程、搜索流程。

    索引流程:采集数据--->构建文档对象--->创建索引(将文档写入索引库)。

    搜索流程:创建查询--->执行搜索--->渲染搜索结果。

    2 入门示例

    2.1 需求

    使用Lucene实现电商项目中图书类商品的索引和搜索功能。

    2.2 配置步骤说明

    (1)搭建环境(先下载Lucene)

    (2)创建索引库

    (3)搜索索引库

    2.3 配置步骤

    2.3.1 第一部分:搭建环境(创建项目,导入包)

    前提:已经创建好了数据库(直接导入book.sql文件)

    43fea02ba640f0a5a398447b110a196f.png

    2.3.1.1 第一步:下载Lucene

    Lucene是开发全文检索功能的工具包,使用时从官方网站下载,并解压。

    下载版本:4.10.3(要求:jdk1.7及以上)

    核心包lucene-core-4.10.3.jar(附常用API)

    15bcf4215bffe41f914d285c92df60c0.png

    2.3.1.2 第二步:创建项目,导入包

    mysql5.1驱动包:mysql-connector-java-5.1.7-bin.jar

    核心包:lucene-core-4.10.3.jar

    分析器通用包:lucene-analyzers-common-4.10.3.jar

    查询解析器包:lucene-queryparser-4.10.3.jar

    项目结构如下:

    476e8f519000c968091234acb43850e5.png

    2.3.2 第二部分:创建索引

    步骤说明:

    (1)采集数据

    (2)将数据转换成Lucene文档

    (3)将文档写入索引库,创建索引

    2.3.2.1 第一步:采集数据

    Lucene全文检索,不是直接查询数据库,所以需要先将数据采集出来。

    (1)创建Book类

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 public classBook {2

    3   private Integer bookId; //图书ID

    4

    5   private String name; //图书名称

    6

    7   private Float price; //图书价格

    8

    9   private String pic; //图书图片

    10

    11   private String description; //图书描述12

    13   //补全get\set方法

    14

    15 }

    View Code

    public class Book {

    private Integer bookId; // 图书ID

    private String name; // 图书名称

    private Float price; // 图书价格

    private String pic; // 图书图片

    private String description; // 图书描述

    // 补全get\set方法

    }

    (2)创建一个BookDao类

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 packagecn.gzsxt.lucene.dao;2

    3 importjava.sql.Connection;4

    5 importjava.sql.DriverManager;6

    7 importjava.sql.PreparedStatement;8

    9 importjava.sql.ResultSet;10

    11 importjava.sql.SQLException;12

    13 importjava.util.ArrayList;14

    15 importjava.util.List;16

    17 importcn.gzsxt.lucene.pojo.Book;18

    19 public classBookDao {20

    21   public ListgetAll() {22

    23     //数据库链接

    24

    25     Connection connection = null;26

    27     //预编译statement

    28

    29     PreparedStatement preparedStatement = null;30

    31     //结果集

    32

    33     ResultSet resultSet = null;34

    35     //图书列表

    36

    37     List list = new ArrayList();38

    39     try{40

    41       //加载数据库驱动

    42

    43       Class.forName("com.mysql.jdbc.Driver");44

    45       //连接数据库

    46

    47       connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/lucene", "root", "gzsxt");48

    49       //SQL语句

    50

    51       String sql = "SELECT * FROM book";52

    53       //创建preparedStatement

    54

    55       preparedStatement =connection.prepareStatement(sql);56

    57       //获取结果集

    58

    59       resultSet =preparedStatement.executeQuery();60

    61       //结果集解析

    62

    63       while(resultSet.next()) {64

    65         Book book = newBook();66

    67         book.setBookId(resultSet.getInt("id"));68

    69         book.setName(resultSet.getString("name"));70

    71         book.setPrice(resultSet.getFloat("price"));72

    73         book.setPic(resultSet.getString("pic"));74

    75         book.setDescription(resultSet.getString("description"));76

    77 list.add(book);78

    79 }80

    81     } catch(Exception e) {82

    83 e.printStackTrace();84

    85     }finally{86

    87       if(null!=resultSet){88

    89         try{90

    91 resultSet.close();92

    93         } catch(SQLException e) {94

    95           //TODO Auto-generated catch block

    96

    97 e.printStackTrace();98

    99 }100

    101 }102

    103       if(null!=preparedStatement){104

    105         try{106

    107 preparedStatement.close();108

    109         } catch(SQLException e) {110

    111           //TODO Auto-generated catch block

    112

    113 e.printStackTrace();114

    115 }116

    117 }118

    119       if(null!=connection){120

    121         try{122

    123 connection.close();124

    125         } catch(SQLException e) {126

    127           //TODO Auto-generated catch block

    128

    129 e.printStackTrace();130

    131 }132

    133 }134

    135 }136

    137     returnlist;138

    139 }140

    141 }

    View Code

    package cn.gzsxt.lucene.dao;

    import java.sql.Connection;

    import java.sql.DriverManager;

    import java.sql.PreparedStatement;

    import java.sql.ResultSet;

    import java.sql.SQLException;

    import java.util.ArrayList;

    import java.util.List;

    import cn.gzsxt.lucene.pojo.Book;

    public class BookDao {

    public List getAll() {

    // 数据库链接

    Connection connection = null;

    // 预编译statement

    PreparedStatement preparedStatement = null;

    // 结果集

    ResultSet resultSet = null;

    // 图书列表

    List list = new ArrayList();

    try {

    // 加载数据库驱动

    Class.forName("com.mysql.jdbc.Driver");

    // 连接数据库

    connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/lucene", "root", "gzsxt");

    // SQL语句

    String sql = "SELECT * FROM book";

    // 创建preparedStatement

    preparedStatement = connection.prepareStatement(sql);

    // 获取结果集

    resultSet = preparedStatement.executeQuery();

    // 结果集解析

    while (resultSet.next()) {

    Book book = new Book();

    book.setBookId(resultSet.getInt("id"));

    book.setName(resultSet.getString("name"));

    book.setPrice(resultSet.getFloat("price"));

    book.setPic(resultSet.getString("pic"));

    book.setDescription(resultSet.getString("description"));

    list.add(book);

    }

    } catch (Exception e) {

    e.printStackTrace();

    }finally {

    if(null!=resultSet){

    try {

    resultSet.close();

    } catch (SQLException e) {

    // TODO Auto-generated catch block

    e.printStackTrace();

    }

    }

    if(null!=preparedStatement){

    try {

    preparedStatement.close();

    } catch (SQLException e) {

    // TODO Auto-generated catch block

    e.printStackTrace();

    }

    }

    if(null!=connection){

    try {

    connection.close();

    } catch (SQLException e) {

    // TODO Auto-generated catch block

    e.printStackTrace();

    }

    }

    }

    return list;

    }

    }

    (3)创建一个测试类BookDaoTest

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 packagecn.gzsxt.lucene.test;2

    3 importjava.util.List;4

    5 importorg.junit.Test;6

    7 importcn.gzsxt.lucene.dao.BookDao;8

    9 importcn.gzsxt.lucene.pojo.Book;10

    11 public classBookDaoTest {12

    13 @Test14

    15 public voidgetAll(){16

    17 BookDao dao = newBookDao();18

    19 List books =dao.getAll();20

    21 for(Book book : books) {22

    23 System.out.println("图书id:"+book.getBookId()+",图书名称:"+book.getName());24

    25 }26

    27 }28

    29 }

    View Code

    package cn.gzsxt.lucene.test;

    import java.util.List;

    import org.junit.Test;

    import cn.gzsxt.lucene.dao.BookDao;

    import cn.gzsxt.lucene.pojo.Book;

    public class BookDaoTest {

    @Test

    public void getAll(){

    BookDao dao = new BookDao();

    List books = dao.getAll();

    for (Book book : books) {

    System.out.println("图书id:"+book.getBookId()+",图书名称:"+book.getName());

    }

    }

    }

    (4)测试结果,采集数据成功

    d152743c9f2e69fb7460f3298b32e105.png

    2.3.2.2 第二步:将数据转换成Lucene文档

    Lucene是使用文档类型来封装数据的,所有需要先将采集的数据转换成文档类型。其格式为:

    048a84e350a2545016894cebeca1aee6.png

    修改BookDao,新增一个方法,转换数据

    public List getDocuments(List books){

    // Document对象集合

    List docList = new ArrayList();

    // Document对象

    Document doc = null;

    for (Book book : books) {

    // 创建Document对象,同时要创建field对象

    doc = new Document();

    // 根据需求创建不同的Field

    Field id = new TextField("id", book.getBookId().toString(), Store.YES);

    Field name = new TextField("name", book.getName(), Store.YES);

    Field price = new TextField("price", book.getPrice().toString(),Store.YES);

    Field pic = new TextField("pic", book.getPic(), Store.YES);

    Field desc = new TextField("description", book.getDescription(), Store.YES);

    // 把域(Field)添加到文档(Document)中

    doc.add(id);

    doc.add(name);

    doc.add(price);

    doc.add(pic);

    doc.add(desc);

    docList.add(doc);

    }

    return docList;

    }

    2.3.2.3 第三步:创建索引库

    说明:Lucene是在将文档写入索引库的过程中,自动完成分词、创建索引的。因此创建索引库,从形式上看,就是将文档写入索引库!

    修改测试类,新增createIndex方法

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 @Test2

    3 public voidcreateIndex(){4

    5   try{6

    7     BookDao dao = newBookDao();8

    9     //分析文档,对文档中的field域进行分词

    10

    11     Analyzer analyzer = newStandardAnalyzer();12

    13     //创建索引14

    15     //1) 创建索引库目录

    16

    17     Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));18

    19     //2) 创建IndexWriterConfig对象

    20

    21     IndexWriterConfig cfg = newIndexWriterConfig(Version.LATEST, analyzer);22

    23     //3) 创建IndexWriter对象

    24

    25     IndexWriter writer = newIndexWriter(directory, cfg);26

    27     //4) 通过IndexWriter对象添加文档对象(document)

    28

    29 writer.addDocuments(dao.getDocuments(dao.getAll()));30

    31     //5) 关闭IndexWriter

    32

    33 writer.close();34

    35     System.out.println("创建索引库成功");36

    37   } catch(Exception e) {38

    39 e.printStackTrace();40

    41 }42

    43 }

    View Code

    @Test

    public void createIndex(){

    try {

    BookDao dao = new BookDao();

    // 分析文档,对文档中的field域进行分词

    Analyzer analyzer = new StandardAnalyzer();

    // 创建索引

    // 1) 创建索引库目录

    Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));

    // 2) 创建IndexWriterConfig对象

    IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST, analyzer);

    // 3) 创建IndexWriter对象

    IndexWriter writer = new IndexWriter(directory, cfg);

    // 4) 通过IndexWriter对象添加文档对象(document)

    writer.addDocuments(dao.getDocuments(dao.getAll()));

    // 5) 关闭IndexWriter

    writer.close();

    System.out.println("创建索引库成功");

    } catch (Exception e) {

    e.printStackTrace();

    }

    }

    测试结果,创建成功!!!

    c89e5f337a210759b167ba9e7894e65a.png

    2.3.3 第三部分:搜索索引

    2.3.3.1 说明

    搜索的时候,需要指定搜索哪一个域(也就是字段),并且,还要对搜索的关键词做分词处理。

    2.3.3.2 执行搜索

    修改测试类,新增searchDocumentByIndex方法

    1 @Test2

    3 public voidsearchDocumentByIndex(){4

    5   try{6

    7     //1、 创建查询(Query对象)8

    9     //创建分析器

    10

    11     Analyzer analyzer = newStandardAnalyzer();12

    13     QueryParser queryParser = new QueryParser("name", analyzer);14

    15     Query query = queryParser.parse("name:java教程");16

    17     //2、 执行搜索18

    19     //a) 指定索引库目录

    20

    21     Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));22

    23     //b) 创建IndexReader对象

    24

    25     IndexReader reader =DirectoryReader.open(directory);26

    27     //c) 创建IndexSearcher对象

    28

    29     IndexSearcher searcher = newIndexSearcher(reader);30

    31     //d) 通过IndexSearcher对象执行查询索引库,返回TopDocs对象32

    33     //第一个参数:查询对象34

    35     //第二个参数:最大的n条记录

    36

    37     TopDocs topDocs = searcher.search(query, 10);38

    39     //e) 提取TopDocs对象中前n条记录

    40

    41     ScoreDoc[] scoreDocs =topDocs.scoreDocs;42

    43     System.out.println("查询出文档个数为:" +topDocs.totalHits);44

    45     for(ScoreDoc scoreDoc : scoreDocs) {46

    47       //文档对象ID

    48

    49       int docId =scoreDoc.doc;50

    51       Document doc =searcher.doc(docId);52

    53       //f) 输出文档内容

    54

    55       System.out.println("===============================");56

    57       System.out.println("文档id:" +docId);58

    59       System.out.println("图书id:" + doc.get("id"));60

    61       System.out.println("图书name:" + doc.get("name"));62

    63       System.out.println("图书price:" + doc.get("price"));64

    65       System.out.println("图书pic:" + doc.get("pic"));66

    67       System.out.println("图书description:" + doc.get("description"));68

    69 }70

    71     //g) 关闭IndexReader

    72

    73 reader.close();74

    75   } catch(Exception e) {76

    77     //TODO Auto-generated catch block

    78

    79 e.printStackTrace();80

    81 }82

    83 }

    @Test

    public void searchDocumentByIndex(){

    try {

    // 1、 创建查询(Query对象)

    // 创建分析器

    Analyzer analyzer = new StandardAnalyzer();

    QueryParser queryParser = new QueryParser("name", analyzer);

    Query query = queryParser.parse("name:java教程");

    // 2、 执行搜索

    // a) 指定索引库目录

    Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));

    // b) 创建IndexReader对象

    IndexReader reader = DirectoryReader.open(directory);

    // c) 创建IndexSearcher对象

    IndexSearcher searcher = new IndexSearcher(reader);

    // d) 通过IndexSearcher对象执行查询索引库,返回TopDocs对象

    // 第一个参数:查询对象

    // 第二个参数:最大的n条记录

    TopDocs topDocs = searcher.search(query, 10);

    // e) 提取TopDocs对象中前n条记录

    ScoreDoc[] scoreDocs = topDocs.scoreDocs;

    System.out.println("查询出文档个数为:" + topDocs.totalHits);

    for (ScoreDoc scoreDoc : scoreDocs) {

    // 文档对象ID

    int docId = scoreDoc.doc;

    Document doc = searcher.doc(docId);

    // f) 输出文档内容

    System.out.println("===============================");

    System.out.println("文档id:" + docId);

    System.out.println("图书id:" + doc.get("id"));

    System.out.println("图书name:" + doc.get("name"));

    System.out.println("图书price:" + doc.get("price"));

    System.out.println("图书pic:" + doc.get("pic"));

    System.out.println("图书description:" + doc.get("description"));

    }

    // g) 关闭IndexReader

    reader.close();

    } catch (Exception e) {

    // TODO Auto-generated catch block

    e.printStackTrace();

    }

    }

    测试结果,非常成功!!!

    b31890263bae64fc17857d9030696085.png

    2.4 小结

    Lucene全文检索,确实可以实现对关键词做分词、再执行搜索功能。并且结果更精确。

    3 分词

    3.1 重要性

    分词是全文检索的核心。

    所谓的分词,就是将一段文本,根据一定的规则,拆分成一个一个词。

    Lucene是根据分析器实现分词的。针对不同的语言提供了不同的分析器。并且提供了一个通用的标准分析器StandardAnalyzer

    3.2 分词过程

    --说明:我们通过分析StandardAnalyzer核心源码来分析分词过程

    1 @Override2

    3 protected TokenStreamComponents createComponents(final String fieldName, finalReader reader) {4

    5   final StandardTokenizer src = newStandardTokenizer(getVersion(), reader);6

    7 src.setMaxTokenLength(maxTokenLength);8

    9   TokenStream tok = newStandardFilter(getVersion(), src);10

    11   tok = newLowerCaseFilter(getVersion(), tok);12

    13   tok = newStopFilter(getVersion(), tok, stopwords);14

    15   return newTokenStreamComponents(src, tok) {16

    17 @Override18

    19     protected void setReader(final Reader reader) throwsIOException {20

    21     src.setMaxTokenLength(StandardAnalyzer.this.maxTokenLength);22

    23     super.setReader(reader);24

    25 }26

    27 };28

    29 }

    @Override

    protected TokenStreamComponents createComponents(final String fieldName, final Reader reader) {

    final StandardTokenizer src = new StandardTokenizer(getVersion(), reader);

    src.setMaxTokenLength(maxTokenLength);

    TokenStream tok = new StandardFilter(getVersion(), src);

    tok = new LowerCaseFilter(getVersion(), tok);

    tok = new StopFilter(getVersion(), tok, stopwords);

    return new TokenStreamComponents(src, tok) {

    @Override

    protected void setReader(final Reader reader) throws IOException {

    src.setMaxTokenLength(StandardAnalyzer.this.maxTokenLength);

    super.setReader(reader);

    }

    };

    }

    对应Lucene分词的过程,我们可以做如下总结:

    (1)分词的时候,是以域为单位的。不同的域,相互独立。

    同一个域中,拆分出来相同的词,视为同一个词(Term)

    不同的域中,拆分出来相同的词,不是同一个词。由下图可以看出,在name域和desc域中都有java。

    其中,Term是Lucene最小的语汇单元,不可再细分。

    (2)分词的时候经历了一系列的过滤器。如大小写转换、去除停用词等。

    3.3 分词后索引库结构

    我们这里借助前面的示例来说明

    a03717141df511e89f7666192beb5bdb.png

    从上图中,我们发现:

    (1)索引库中有两个区域:索引区、文档区。

    (2)文档区存放的是文档。Lucene给每一个文档自动加上一个文档编号docID。

    (3)索引区存放的是索引。注意:

    索引是以域为单位的,不同的域,彼此相互独立。

    索引是根据分词规则创建出来的,根据索引就能找到对应的文档。

    3.4 Luke客户端连接索引库

    Luke作为Lucene工具包中的一个工具(http://www.getopt.org/luke/),可以通过可视化界面,连接操作索引库。

    3.4.1 启动方法

    (1)双击start.bat启动!

    0a492b71301491e275fee1af654add37.png

    (2)连接索引库

    5d7e703519e78de52fb35935c49ac28a.png

    3.4.2 验证分词效果

    434aa0e2c7933193654a9de44fe05453.png

    4 Field域

    问题:我们已经知道,Lucene是在写入文档时,完成分词、索引的。那Lucene是怎么知道如何分词的呢?

    答:Lucene是根据文档中的域的属性,来确定是否要分词、创建索引的。所以,我们必须搞清楚域有哪些属性。

    4.1 域的属性

    4.1.1 三大属性

    4.1.1.1 是否分词(tokenized)

    只有设置了分词属性为true,lucene才会对这个域进行分词处理。

    在实际的开发中,有一些字段是不需要分词的,比如商品id,商品图片等。

    而有一些字段是必须分词的,比如商品名称,描述信息等。

    4.1.1.2 是否索引(indexed)

    只有设置了索引属性为true,lucene才为这个域的Term词创建索引。

    在实际的开发中,有一些字段是不需要创建索引的,比如商品的图片等。我们只需要对参与搜索的字段做索引处理。

    4.1.1.3 是否存储(stored)

    只有设置了存储属性为true,在查找的时候,才能从文档中获取这个域的值。

    在实际开发中,有一些字段是不需要存储的。比如:商品的描述信息。

    因为商品描述信息,通常都是大文本数据,读的时候会造成巨大的IO开销。而描述信息是不需要经常查询的字段,这样的话就白白浪费了cpu的资源了。

    因此,像这种不需要经常查询,又是大文本的字段,通常不会存储到索引库。

    4.1.2 特点

    (1)三大属性彼此独立。

    (2)通常分词是为了创建索引。

    (3)不存储这个域文本内容,也可以对这个域先分词、创建索引。

    4.2 Field常用类型

    域的常用类型有很多,每一个类都有自己默认的三大属性。如下:

    Field类

    数据类型

    Analyzed

    是否分词

    Indexed

    是否索引

    Stored

    是否存储

    StringField(FieldName, FieldValue,Store.YES))

    字符串

    N

    Y

    Y或N

    LongField(FieldName, FieldValue,Store.YES)

    Long型

    Y

    Y

    Y或N

    FloatField(FieldName, FieldValue,Store.YES)

    Float型

    Y

    Y

    Y或N

    StoredField(FieldName, FieldValue)

    重载方法,支持多种类型

    N

    N

    Y

    TextField(FieldName, FieldValue, Store.NO)

    字符串

    Y

    Y

    Y或N

    (不单单是这些,还有像DoubleField等等)

    4.3 改造入门示例中的域类型

    4.3.1 分析

    (1)图书id:

    是否分词:不用分词,因为不会根据商品id来搜索商品

    是否索引:不索引,因为不需要根据图书ID进行搜索

    是否存储:要存储,因为查询结果页面需要使用id这个值。

    (2)图书名称:

    是否分词:要分词,因为要将图书的名称内容分词索引,根据关键搜索图书名称抽取的词。

    是否索引:要索引。

    是否存储:要存储。

    (3)图书价格:

    是否分词:要分词,lucene对数字型的值只要有搜索需求的都要分词和索引,因为lucene对数字型的内容要特殊分词处理,本例子可能要根据价格范围搜索, 需要分词和索引。

    是否索引:要索引

    是否存储:要存储

    (4)图书图片地址:

    是否分词:不分词

    是否索引:不索引

    是否存储:要存储

    (5)图书描述:

    是否分词:要分词

    是否索引:要索引

    是否存储:因为图书描述内容量大,不在查询结果页面直接显示,不存储。

    不存储是来不在lucene的索引文件中记录,节省lucene的索引文件空间,如果要在详情页面显示描述,思路:

    从lucene中取出图书的id,根据图书的id查询关系数据库中book表得到描述信息。

    4.3.2 代码修改

    修改BookDao的getDocument方法

    8f900a89c6347c561fdf2122f13be562.png

    961ddebeb323a10fe0623af514929fc1.png

    1 public List getDocuments(Listbooks){2

    3   //Document对象集合

    4

    5   List docList = new ArrayList();6

    7   //Document对象

    8

    9   Document doc = null;10

    11   for(Book book : books) {12

    13     //创建Document对象,同时要创建field对象

    14

    15     doc = newDocument();16

    17     //图书ID18

    19     //参数:域名、域中存储的内容、是否存储20

    21     //不分词、索引、要存储22

    23     //Field id = new TextField("id", book.getId().toString(),Store.YES);

    24

    25     Field id = new StoredField("id", book.getBookId().toString());26

    27     //图书名称28

    29     //分词、索引、存储

    30

    31     Field name = new TextField("name", book.getName(),Store.YES);32

    33     //图书价格34

    35     //分词、索引、存储

    36

    37     Field price = new FloatField("price", book.getPrice(), Store.YES);38

    39     //图书图片40

    41     //不分词、不索引、要存储

    42

    43     Field pic = new StoredField("pic", book.getPic());44

    45     //图书描述46

    47     //分词、索引、不存储

    48

    49     Field desc = new TextField("description",book.getDescription(), Store.NO);50

    51     //把域(Field)添加到文档(Document)中

    52

    53 doc.add(id);54

    55 doc.add(name);56

    57 doc.add(price);58

    59 doc.add(pic);60

    61 doc.add(desc);62

    63 docList.add(doc);64

    65 }66

    67   returndocList;68

    69 }

    View Code

    public List getDocuments(List books){

    // Document对象集合

    List docList = new ArrayList();

    // Document对象

    Document doc = null;

    for (Book book : books) {

    // 创建Document对象,同时要创建field对象

    doc = new Document();

    // 图书ID

    // 参数:域名、域中存储的内容、是否存储

    // 不分词、索引、要存储

    // Field id = new TextField("id", book.getId().toString(),Store.YES);

    Field id =newStoredField("id", book.getBookId().toString());

    // 图书名称

    // 分词、索引、存储

    Field name =newTextField("name", book.getName(),Store.YES);

    // 图书价格

    // 分词、索引、存储

    Field price =newFloatField("price", book.getPrice(), Store.YES);

    // 图书图片

    // 不分词、不索引、要存储

    Field pic =newStoredField("pic", book.getPic());

    // 图书描述

    // 分词、索引、不存储

    Field desc =newTextField("description",book.getDescription(), Store.NO);

    // 把域(Field)添加到文档(Document)中

    doc.add(id);

    doc.add(name);

    doc.add(price);

    doc.add(pic);

    doc.add(desc);

    docList.add(doc);

    }

    return docList;

    }

    4.3.3 测试

    (1)去索引库目录中,手动清空索引库。

    (2)重新创建索引库。

    (3)使用Luke验证分词、索引效果。

    2b09e029cd2c98e03abf65181030153b.png

    改造成功!!!

    5 索引库维护

    在第4节,我们需要重新创建索引的时候,是去索引库目录下,手动删除的。

    而在实际的开发中,我们可能压根就不知道索引库在哪,就算知道,我们也不可能每次都去手动删除,非常之麻烦!!!

    所以,我们必须学习如何维护索引库,使用程序来操作索引库。

    需要注意的是,索引是与文档紧密相连的,因此对索引的维护,实际上就是对文档的增删改。

    5.1 添加索引(文档)

    5.1.1 需求

    数据库中新上架了图书,必须把这些图书也添加到索引库中,不然就搜不到该新上架的图书了。

    5.1.2 代码实现

    调用 indexWriter.addDocument(doc)添加索引。

    参考入门示例中的创建索引。

    5.2 删除索引(文档)

    5.2.1 需求

    某些图书不再出版销售了,我们需要从索引库中移除该图书。

    5.2.2 代码实现

    1@Test2

    3 public void deleteIndex() throwsException {4

    5   //1、指定索引库目录

    6

    7   Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));8

    9   //2、创建IndexWriterConfig

    10

    11   IndexWriterConfig cfg = newIndexWriterConfig(Version.LATEST,12

    13   newStandardAnalyzer());14

    15   //3、 创建IndexWriter

    16

    17   IndexWriter writer = newIndexWriter(directory, cfg);18

    19   //4、通过IndexWriter来删除索引20

    21   //删除指定索引

    22

    23   writer.deleteDocuments(new Term("name", "apache"));24

    25   //5、关闭IndexWriter

    26

    27 writer.close();28

    29   System.out.println("删除成功");30

    31 }

    @Test

    public void deleteIndex() throws Exception {

    // 1、指定索引库目录

    Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));

    // 2、创建IndexWriterConfig

    IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST,

    new StandardAnalyzer());

    // 3、 创建IndexWriter

    IndexWriter writer = new IndexWriter(directory, cfg);

    // 4、通过IndexWriter来删除索引

    // 删除指定索引

    writer.deleteDocuments(newTerm("name", "apache"));

    // 5、关闭IndexWriter

    writer.close();

    System.out.println("删除成功");

    }

    5.2.3 清空索引库

    1@Test2

    3 public void deleteIndex() throwsException {4

    5   //1、指定索引库目录

    6

    7   Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));8

    9   //2、创建IndexWriterConfig

    10

    11   IndexWriterConfig cfg = newIndexWriterConfig(Version.LATEST,12

    13   newStandardAnalyzer());14

    15   //3、 创建IndexWriter

    16

    17   IndexWriter writer = newIndexWriter(directory, cfg);18

    19   //4、通过IndexWriter来删除索引20

    21   //删除指定索引

    22

    23 writer.deleteAll();24

    25   //5、关闭IndexWriter

    26

    27 writer.close();28

    29   System.out.println("清空索引库成功");30

    31 }

    @Test

    public void deleteIndex() throws Exception {

    // 1、指定索引库目录

    Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));

    // 2、创建IndexWriterConfig

    IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST,

    new StandardAnalyzer());

    // 3、 创建IndexWriter

    IndexWriter writer = new IndexWriter(directory, cfg);

    // 4、通过IndexWriter来删除索引

    // 删除指定索引

    writer.deleteAll();

    // 5、关闭IndexWriter

    writer.close();

    System.out.println("清空索引库成功");

    }

    5.3 更新索引(文档)

    5.3.1 说明

    Lucene更新索引比较特殊,是先删除满足条件的索引,再添加新的索引。

    5.3.2 代码实现

    1 //修改索引

    2

    3 @Test4

    5 public void updateIndex() throwsException {6

    7   //1、指定索引库目录

    8

    9   Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));10

    11   //2、创建IndexWriterConfig

    12

    13   IndexWriterConfig cfg = newIndexWriterConfig(Version.LATEST,14

    15   newStandardAnalyzer());16

    17   //3、 创建IndexWriter

    18

    19   IndexWriter writer = newIndexWriter(directory, cfg);20

    21   //4、通过IndexWriter来修改索引22

    23   //a)、创建修改后的文档对象

    24

    25   Document document = newDocument();26

    27   //文件名称

    28

    29   Field filenameField = new StringField("name", "updateIndex", Store.YES);30

    31 document.add(filenameField);32

    33   //修改指定索引为新的索引

    34

    35   writer.updateDocument(new Term("name", "apache"), document);36

    37   //5、关闭IndexWriter

    38

    39 writer.close();40

    41   System.out.println("更新成功");42

    43 }

    // 修改索引

    @Test

    public void updateIndex() throws Exception {

    // 1、指定索引库目录

    Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));

    // 2、创建IndexWriterConfig

    IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST,

    new StandardAnalyzer());

    // 3、 创建IndexWriter

    IndexWriter writer = new IndexWriter(directory, cfg);

    // 4、通过IndexWriter来修改索引

    // a)、创建修改后的文档对象

    Document document = new Document();

    // 文件名称

    Field filenameField = new StringField("name", "updateIndex", Store.YES);

    document.add(filenameField);

    // 修改指定索引为新的索引

    writer.updateDocument(new Term("name", "apache"), document);

    // 5、关闭IndexWriter

    writer.close();

    System.out.println("更新成功");

    }

    6 搜索

    问题:我们在入门示例中,已经知道Lucene是通过IndexSearcher对象,来执行搜索的。那我们为什么还要继续学习Lucene的查询呢?

    答:因为在实际的开发中,我们的查询的业务是相对复杂的,比如我们在通过关键词查找的时候,往往进行价格、商品类别的过滤。

    而Lucene提供了一套查询方案,供我们实现复杂的查询。

    6.1 创建查询的两种方法

    执行查询之前,必须创建一个查询Query查询对象。

    Query自身是一个抽象类,不能实例化,必须通过其它的方式来实现初始化。

    在这里,Lucene提供了两种初始化Query查询对象的方式。

    6.1.1 使用Lucene提供Query子类

    Query是一个抽象类,lucene提供了很多查询对象,比如TermQuery项精确查询,NumericRangeQuery数字范围查询等。

    使用TermQuery实例化

    Query query = new TermQuery(new Term("name", "lucene"));

    6.1.2 使用QueryParse解析查询表达式

    QueryParser会将用户输入的查询表达式解析成Query对象实例。如下代码:

    QueryParser queryParser = new QueryParser("name", new IKAnalyzer());

    Query query = queryParser.parse("name:lucene");

    6.2 常用的Query子类搜索

    6.2.1 TermQuery

    特点:查询的关键词不会再做分词处理,作为整体来搜索。代码如下:

    1 /**

    2

    3 * Query子类查询之 TermQuery4

    5 *6

    7 * 特点:不会再对查询的关键词做分词处理。8

    9 *10

    11 * 需要:查询书名与java教程相关书。12

    13 */

    14

    15 @Test16

    17 public voidqueryByTermQuery(){18

    19   //1、获取一个查询对象

    20

    21   Query query = new TermQuery(new Term("name", "编程思想"));22

    23 doSearch(query);24

    25 }26

    27 private voiddoSearch(Query query) {28

    29   try{30

    31     //2、创建一个查询的执行对象32

    33     //指定索引库的目录

    34

    35     Directory d = FSDirectory.open(new File("F:\\lucene\\0719"));36

    37     //创建流对象

    38

    39     IndexReader reader =DirectoryReader.open(d);40

    41     //创建搜索执行对象

    42

    43     IndexSearcher searcher = newIndexSearcher(reader);44

    45     //3、执行搜索

    46

    47     TopDocs result = searcher.search(query, 10);48

    49     //4、提出结果集,获取图书的信息

    50

    51     int totalHits =result.totalHits;52

    53     System.out.println("共查询到"+totalHits+"条满足条件的数据!");54

    55     System.out.println("-----------------------------------------");56

    57     //提取图书信息。58

    59     //score即相关度。即搜索的关键词和 图书名称的相关度,用来做排序处理

    60

    61     ScoreDoc[] scoreDocs =result.scoreDocs;62

    63     for(ScoreDoc scoreDoc : scoreDocs) {64

    65       /**

    66

    67 * scoreDoc.doc的返回值,是文档的id, 即 将文档写入索引库的时候,lucene自动给这份文档做的一个编号。68

    69 *70

    71 * 获取到这个文档id之后,即可以根据这个id,找到这份文档。72

    73       */

    74

    75       int docId =scoreDoc.doc;76

    77       System.out.println("文档在索引库中的编号:"+docId);78

    79       //从文档中提取图书的信息

    80

    81       Document doc =searcher.doc(docId);82

    83       System.out.println("图书id:"+doc.get("id"));84

    85       System.out.println("图书name:"+doc.get("name"));86

    87       System.out.println("图书price:"+doc.get("price"));88

    89       System.out.println("图书pic:"+doc.get("pic"));90

    91       System.out.println("图书description:"+doc.get("description"));92

    93 System.out.println();94

    95       System.out.println("------------------------------------");96

    97 }98

    99     //关闭连接,释放资源

    100

    101     if(null!=reader){102

    103 reader.close();104

    105 }106

    107   } catch(Exception e) {108

    109 e.printStackTrace();110

    111 }112

    113 }

    /**

    * Query子类查询之 TermQuery

    *

    * 特点:不会再对查询的关键词做分词处理。

    *

    * 需要:查询书名与java教程相关书。

    */

    @Test

    public void queryByTermQuery(){

    //1、获取一个查询对象

    Query query = new TermQuery(new Term("name", "编程思想"));

    doSearch(query);

    }

    private void doSearch(Query query) {

    try {

    //2、创建一个查询的执行对象

    //指定索引库的目录

    Directory d = FSDirectory.open(new File("F:\\lucene\\0719"));

    //创建流对象

    IndexReader reader = DirectoryReader.open(d);

    //创建搜索执行对象

    IndexSearcher searcher = new IndexSearcher(reader);

    //3、执行搜索

    TopDocs result = searcher.search(query, 10);

    //4、提出结果集,获取图书的信息

    int totalHits = result.totalHits;

    System.out.println("共查询到"+totalHits+"条满足条件的数据!");

    System.out.println("-----------------------------------------");

    //提取图书信息。

    //score即相关度。即搜索的关键词和 图书名称的相关度,用来做排序处理

    ScoreDoc[] scoreDocs = result.scoreDocs;

    for (ScoreDoc scoreDoc : scoreDocs) {

    /**

    * scoreDoc.doc的返回值,是文档的id, 即 将文档写入索引库的时候,lucene自动给这份文档做的一个编号。

    *

    * 获取到这个文档id之后,即可以根据这个id,找到这份文档。

    */

    int docId = scoreDoc.doc;

    System.out.println("文档在索引库中的编号:"+docId);

    //从文档中提取图书的信息

    Document doc = searcher.doc(docId);

    System.out.println("图书id:"+doc.get("id"));

    System.out.println("图书name:"+doc.get("name"));

    System.out.println("图书price:"+doc.get("price"));

    System.out.println("图书pic:"+doc.get("pic"));

    System.out.println("图书description:"+doc.get("description"));

    System.out.println();

    System.out.println("------------------------------------");

    }

    //关闭连接,释放资源

    if(null!=reader){

    reader.close();

    }

    } catch (Exception e) {

    e.printStackTrace();

    }

    }

    6.2.2 NumericRangeQuery

    指定数字范围查询.(创建field类型时,注意与之对应)

    1 /**

    2

    3 * Query子类查询 之 NumricRangeQuery4

    5 * 需求:查询所有价格在[60,80)之间的书6

    7 *@paramquery8

    9 */

    10

    11 @Test12

    13 public voidqueryByNumricRangeQuery(){14

    15   /**

    16

    17 * 第一个参数:要搜索的域18

    19 * 第二个参数:最小值20

    21 * 第三个参数:最大值22

    23 * 第四个参数:是否包含最小值24

    25 * 第五个参数:是否包含最大值26

    27   */

    28

    29   Query query = NumericRangeQuery.newFloatRange("price", 60.0f, 80.0f, true, false);30

    31 doSearch(query);32

    33 }

    /**

    * Query子类查询  之  NumricRangeQuery

    * 需求:查询所有价格在[60,80)之间的书

    * @param query

    */

    @Test

    public void queryByNumricRangeQuery(){

    /**

    * 第一个参数:要搜索的域

    * 第二个参数:最小值

    * 第三个参数:最大值

    * 第四个参数:是否包含最小值

    * 第五个参数:是否包含最大值

    */

    Query query = NumericRangeQuery.newFloatRange("price", 60.0f, 80.0f, true, false);

    doSearch(query);

    }

    6.2.3 BooleanQuery

    BooleanQuery,布尔查询,实现组合条件查询。

    1 /**

    2

    3 * Query子类查询 之 BooelanQuery查询 组合条件查询4

    5 *6

    7 * 需求:查询书名包含java,并且价格区间在[60,80)之间的书。8

    9 */

    10

    11 @Test12

    13 public voidqueryBooleanQuery(){14

    15   //1、要使用BooelanQuery查询,首先要把单个创建出来,然后再通过BooelanQuery组合

    16

    17   Query price = NumericRangeQuery.newFloatRange("price", 60.0f, 80.0f, true, false);18

    19   Query name = new TermQuery(new Term("name", "java"));20

    21   //2、创建BooleanQuery实例对象

    22

    23   BooleanQuery query = newBooleanQuery();24

    25 query.add(name, Occur.MUST_NOT);26

    27 query.add(price, Occur.MUST);28

    29   /**

    30

    31 * MSUT 表示必须满足 对应的是 +32

    33 * MSUT_NOT 必须不满足 应对的是 -34

    35 * SHOULD 可以满足也可以不满足 没有符号36

    37 *38

    39 * SHOULD 与MUST、MUST_NOT组合的时候,SHOULD就没有意义了。40

    41   */

    42

    43 doSearch(query);44

    45 }

    /**

    * Query子类查询  之  BooelanQuery查询   组合条件查询

    *

    * 需求:查询书名包含java,并且价格区间在[60,80)之间的书。

    */

    @Test

    public void queryBooleanQuery(){

    //1、要使用BooelanQuery查询,首先要把单个创建出来,然后再通过BooelanQuery组合

    Query price = NumericRangeQuery.newFloatRange("price", 60.0f, 80.0f, true, false);

    Query name = new TermQuery(new Term("name", "java"));

    //2、创建BooleanQuery实例对象

    BooleanQuery query = new BooleanQuery();

    query.add(name, Occur.MUST_NOT);

    query.add(price, Occur.MUST);

    /**

    * MSUT  表示必须满足                          对应的是  +

    * MSUT_NOT  必须不满足                   应对的是  -

    * SHOULD  可以满足也可以不满足     没有符号

    *

    * SHOULD 与MUST、MUST_NOT组合的时候,SHOULD就没有意义了。

    */

    doSearch(query);

    }

    6.3 通过QueryParser搜索

    6.3.1 特点

    对搜索的关键词,做分词处理。

    6.3.2 语法

    6.3.2.1 基础语法

    域名:关键字

    实例:name:java

    6.3.2.2 组合条件语法

    条件1 AND 条件2

    条件1 OR 条件2

    条件1 NOT 条件2

    6.3.3 QueryParser

    6.3.3.1 代码实现

    1 /**

    2

    3 * 查询解析器查询 之 QueryParser查询4

    5 */

    6

    7 @Test8

    9 public voidqueryByQueryParser(){10

    11   try{12

    13     //1、加载分词器

    14

    15     Analyzer analyzer = newStandardAnalyzer();16

    17     /**

    18

    19 * 2、创建查询解析器实例对象20

    21 * 第一个参数:默认搜索的域。22

    23 * 如果在搜索的时候,没有特别指定搜索的域,则按照默认的域进行搜索24

    25 * 如何在搜索的时候指定搜索域呢?26

    27 * 答:格式 域名:关键词 即 name:java教程28

    29 *30

    31 * 第二个参数:分词器 ,对关键词做分词处理32

    33     */

    34

    35     QueryParser parser = new QueryParser("description", analyzer);36

    37     //设置组合条件查询38

    39     //Query query = queryParser.parse("name:java教程 or description:apache");40

    41     //Query query = queryParser.parse("name:java教程 or apache");

    42

    43     Query query = parser.parse("name:java教程");44

    45 doSearch(query);46

    47   } catch(Exception e) {48

    49 e.printStackTrace();50

    51 }52

    53 }

    /**

    * 查询解析器查询  之  QueryParser查询

    */

    @Test

    public void queryByQueryParser(){

    try {

    //1、加载分词器

    Analyzer analyzer = new StandardAnalyzer();

    /**

    * 2、创建查询解析器实例对象

    * 第一个参数:默认搜索的域。

    *          如果在搜索的时候,没有特别指定搜索的域,则按照默认的域进行搜索

    *          如何在搜索的时候指定搜索域呢?

    *          答:格式  域名:关键词        即   name:java教程

    *

    * 第二个参数:分词器   ,对关键词做分词处理

    */

    QueryParser parser = new QueryParser("description", analyzer);

    // 设置组合条件查询

    //      Query query = queryParser.parse("name:java教程 or description:apache");

    // Query query = queryParser.parse("name:java教程 or apache");

    Query query = parser.parse("name:java教程");

    doSearch(query);

    } catch (Exception e) {

    e.printStackTrace();

    }

    }

    6.3.4 MultiFieldQueryParser

    通过MulitFieldQueryParse对多个域查询。

    1 /**

    2

    3 * 查询解析器查询 之 MultiFieldQueryParser查询4

    5 *6

    7 * 特点:同时指定多个搜索域,并且对关键做分词处理8

    9 */

    10

    11 @Test12

    13 public voidqueryByMultiFieldQueryParser(){14

    15   try{16

    17     //1、定义多个搜索的 name、description

    18

    19     String[] fields = {"name","description"};20

    21     //2、加载分词器

    22

    23     Analyzer analyzer = newStandardAnalyzer();24

    25     //3、创建 MultiFieldQueryParser实例对象

    26

    27     MultiFieldQueryParser mParser = newMultiFieldQueryParser(fields, analyzer);28

    29     Query query = mParser.parse("lucene教程");30

    31 doSearch(query);32

    33   } catch(Exception e) {34

    35 e.printStackTrace();36

    37 }38

    39 }

    /**

    * 查询解析器查询  之  MultiFieldQueryParser查询

    *

    *     特点:同时指定多个搜索域,并且对关键做分词处理

    */

    @Test

    public void queryByMultiFieldQueryParser(){

    try {

    //1、定义多个搜索的  name、description

    String[] fields = {"name","description"};

    //2、加载分词器

    Analyzer analyzer = new StandardAnalyzer();

    //3、创建 MultiFieldQueryParser实例对象

    MultiFieldQueryParser mParser = new MultiFieldQueryParser(fields, analyzer);

    Query query = mParser.parse("lucene教程");

    doSearch(query);

    } catch (Exception e) {

    e.printStackTrace();

    }

    }

    展开全文
  • LINQ 中的 I 到底怎么读?字典上也查不到啊。 以 android 这个单词为基础,可以学会多少新的单词? edition 和 version 都是版本,到底有什么区别? 为什么《代码大全》不是包含大量代码的书?代码大全这个名字有...
    • 是 compiler 还是 complier?英文的拼写有什么规律?
    • LINQ 中的 I 到底怎么读?字典上也查不到啊。
    • 以 android 这个单词为基础,可以学会多少新的单词?
    • edition 和 version 都是版本,到底有什么区别?
    • 为什么《代码大全》不是包含大量代码的书?代码大全这个名字有什么梗?
    • 要流畅看技术文档,究竟需要背多少单词?
    • 程序员在工作之余怎么有效背单词,只有中学英文基础,四级已经忘光了怎么逆袭英文?

    阅读全文: http://gitbook.cn/gitchat/activity/59fc7dc6c77fd860b1bc1bab

    您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

    FtooAtPSkEJwnW-9xkCLqSTRpBKX

    展开全文
  • 懒得看文章直接找repo的话点这里前言最近想到了文言文编程这个脑洞,正好又看到Haskell里面有Parsec这个,然后就决定试一下了orz所以打算从最简单的开始入手,也就是将有一定格式的文言文翻译成Python的代码,比如...

    懒得看文章直接找repo的话点这里

    前言

    最近想到了文言文编程这个脑洞,正好又看到Haskell里面有Parsec这个包,然后就决定试一下了orz

    所以打算从最简单的开始入手,也就是将有一定格式的文言文翻译成Python的代码,比如像下面这个(插入排序):

    有略名 排序 其参名 数列 其文曰

    凡 巡 之于 数数自 1 至 求长于 数列 之中 所得之数 是也 为

    媒 者 数列 诸 巡 位之数 也

    今 者 巡 减 1 也

    复为之,方 今 非小于 0 与 媒 小于 数列 诸 今 位之数 乃止

    数列 诸 今 加 1 所得之数 位之数 换作 数列 诸 今 位之数

    今 谪 1 也

    数列 诸 今 加 1 位之数 换作 媒 也

    可以看到整体的样式是和Python的代码是一致的,这样的话可以减少我们写的解释器的工作量,但缺点的话,很明显,并不像自然语言。。。

    一开始看上去很简单

    首先我们来看看Text.ParserCombinators.Parsec这一Package,它可以将一大段文字的每一行和每一个单词提取出来建成一个二维数组,这歌其实和

    parse :: String -> [[String]]

    parse x = map words $ lines x

    这样的一段代码差不多,不过有一个区别就是Parsec里面的parse会把每一个空格也记录起来,这样我们家可以写出这样的一个函数将输入的内容分成每一个词:

    wyFile = endBy line eol

    line = sepBy cell (char ' ')

    cell = many (noneOf " \n")

    eol = char '\n'

    -- convert to single word

    parseTo :: String -> Either ParseError [[String]]

    parseTo = parse wyFile "(unknown)"

    然后我们要建立一个文言文和Python代码之间对应的一个表,这个在Words.hs里面,简单来说就是一个tuple的数组而已(像下面这个)

    keywords :: [(String, String)]

    keywords =

    [

    ("者", "="),

    ("今乃", "="),

    ("换作", "="),

    ("也", " ")

    -- etc

    ]

    然后我们要做的就是写一个将识别到的文言指令替换成Python代码的函数:

    -- replace Wenyan sytax with Python syntax

    replace :: (Eq a) => a -> [(a, a)] -> a

    replace x ((a, b):ys)

    | x == a = b

    | x /= a && ys /= [] = replace x ys

    | otherwise = x

    replaceList :: (Eq a) => [a] -> [(a, a)] -> [a]

    replaceList [] _ = []

    replaceList xs ys = map (`replace` ys) xs

    这段代码也不难理解,简单来说就是历尽里面的每一个词语然后将符合的词进行替换。

    最后我们只要将这个List转换回有格式的String再输出就可以了

    -- convert back to normal format

    parseBack :: [[String]] -> String

    parseBack x = unlines $ map unwords x

    但是事实并非如此

    因为Python并不支持中文的变量名和函数名,所以我们还要进一步操作。

    首先是变量名, 因为Python定义变量并不需要在前面加上如let之类的,所以一个折中的办法就是让写代码的人先在一行提前声明之后需要用到的中文变量名,也就是所提到的:有参者 <...>这个语句。寻找这个语句的方式其实也是历遍。。。

    findVar :: [[String]] -> [String]

    findVar (x:xs)

    | null x = findVar xs

    | head x == varkey = tail x

    | otherwise = findVar xs

    transVar :: [String] -> [(String,String)]

    transVar [] = []

    transVar (x:xs) = let l = length xs in (x, "var" ++ show l) : transVar xs

    接下来便是将中文的变量名转换成英文,我选择的办法是数出有多少个变量然后统一以var为开头命名,后面加上编号。

    同样会遇到问题的就是函数和类的命名,这里的话方法也是类似的,感兴趣的话可以参考一下源代码这里就不展示了。

    到了这里整个程序基本上是写完了的。

    最后翻译出来长这个样子:

    def fun0 ( var4 ):

    for var3 in range( 1 , len( var4 ) ) :

    var2 = var4 [ var3 ]

    var1 = var3 - 1

    while var1 >= 0 and var2 < var4 [ var1 ] :

    var4 [ var1 + 1 ] = var4 [ var1 ]

    var1 -= 1

    var4 [ var1 + 1 ] = var2

    结语

    总的来说整个程序要的思路其实并不难,加上Haskell的Higher Order Function这一个利器,整个主程序的代码才80行不到XD

    当然这个程序还是存在相当多的不足,像是只是支持很少的函数,以及非常多不符合自然语言习惯的内容。不过最麻烦的还是缩进,这个在未来的版本肯定是要去掉的!

    展开全文
  • 完最后的感觉仿佛自己打通了任督二脉,很多以前工作上的问题一下子茅塞顿开。 一个系统从零到一,以前知道大概怎么做,现在总算明白一些为什么这么做以及使用UML的好处是什么? 虽然书中有部分内容自己并不完全...

    读完最后的感觉仿佛自己打通了任督二脉,很多以前工作上的问题一下子茅塞顿开。
    一个系统从零到一,以前知道大概怎么做,现在总算明白一些为什么这么做以及使用UML的好处是什么?
    虽然书中有部分内容自己并不完全赞同,但这本书整体的思想上给了我很大的启发。

    总的来说,抛开具体的工具和概念,我觉得这本书给了我以下几点启示:
    1.UML是一种语言
    语言是用来沟通的主要方式,包含了单词和语法
    UML 的单词就是各种元素、视图和模型,语法就是建模的方法
    语言最基本的功能是能清楚地表达和传递信息
    UML最基本的就是通过模型将需要做的系统清楚表示出来

    2.UML采用的是面向对象的方法
    面向对象是一种认知世界的方法,在面向对象方法里,一切有名字的东西都是对象
    对象和对象之间彼此独立,只有在某个特定场景下,它们的某一个特定实例才相互联系在一起
    每个对象都是一个整体,内部不可分割,外部只能通过边界和其他对象对接
    对象参与每个场景时都会展现其某一个方面性质,这方面性质都可以抽象出来

    3.建模的实质是将现实世界抽象为模型
    同样的事物在不同的世界观的人眼里会产生不同的结果,而这个世界观在建模里对应的是抽象角度
    一旦决定了抽象角度,就确定了一个目标
    一个由抽象角度确定了的目标需要由静态事物加上特定条件下产生的一个特定场景来完成,即静态的事情(物)+特定的条件(规则)+特定的动作(参与者的驱动)=特定的场景(事件)
    建模其实也就是由人+事+物+规则,将现实世界抽象为模型

    4.项目的启动
    项目的启动首先源自“老大”的愿景,在老大看来,为什么要开发这个系统
    然后是进行涉众分析,谁关心这个系统?会涉及到他的什么利益?
    再次是投入,愿意花多少钱,允许多少时间?
    最后是风险,比如客户参与少;没有度量方式;技术风险;重要人物反对;以前曾被取消过……

    5.客户访谈技巧
    一般来说系统分析员是计算机专家,习惯于从计算机角度来看问题;客户是业务专家,习惯身边的人也熟悉这个业务领域。
    所以系统分析员需要改变自己的立场,了解业务是什么,还要弄清业务为什么。
    首先,应该有一个详细的涉众分析报告,循序渐进地从接触到深入了解客户;
    其次,不要急于深入业务细节,而是先就一些大框框进行沟通,借此习惯和了解对方的沟通方式;
    再次,双方的时间是有限的,因此每一次会面都需要争分夺秒,用最快的时间把问题搞清楚;
    最后,系统分析员需要承担每次会谈结果记录,并且需要正式的反馈和确认,以便之后和客户在需求变更时候有依据。

    6.需求获取
    在对每个业务进行需求调研时候首先要明确该业务的边界,每个边界的划分则指明了需求分析的起点
    找到业务主角,访谈业务主角或者从业务主角的角度来看与系统的交互,就可以得到业务用例
    根据业务用例用合适的视图表达出来就构建除了业务模型
    业务建模常见的视图有两种:活动图和时序图
    活动图中活动是主要的内容,表达的内容是业务主角或业务工人做什么;时序图中,消息是主要的内容,表达的内容是业务主角或业务工人之间传递的是什么

    7.需求分析
    需求分析首先要找到关键概念,关键概念是指支撑起客户整个业务架构的那条主线,在UML方法里,就是由一些关键的业务用例构成
    根据各个关键概念,梳理出相关的概念用例,获取概念模型
    每个概念模型表示一个功能,各个概念模型之间通过软件架构联系起来
    从概念模型入手,根据找到业务主线找到每个大的业务构件,再通过领域模型分解为小的构件,再根据概念模型找到各个小的构件之间的依赖关系。
    如此就得到了项目的业务架构

    8.系统分析和设计
    将每个业务模型抽象为描述系统的模型,就得到了系统模型
    业务用例抽象为系统用例的基本方法有:映射、抽象、合并、拆分、演绎等
    系统分析的成果是获取到系统的每个分析类,这些分析类基本上可以分为实体、边界、控制三类,和开发中的MVC正好对应
    将分析类的成果考虑具体实现语言和实现方式,也就是系统设计,得到的成果就是开发中可直接用的类、包和接口

    9.理论和实际
    《大象》这本书尽管作者举了很多生动的例子,画了大量的图,但整体来说是依然是非常枯燥的
    对于书中理论,抽象程度都比较高,专业的词汇也比较多,所以看下去真的很难
    不过UML作为目前可视化建模语言的工业标准,其本身也源于面向对象分析和设计的方法
    目的就是为了更清晰、简单的表达软件设计中的动态和静态信息
    所以如果能结果实际项目来看会往往觉得豁然开朗的感觉,但强行去从概念理解往往会让自己更懵
    另外我觉得uml的学习最大的成果是有这样一种思维方式,如何分析和设计一个系统
    但真正在无比推崇“敏捷开发”的现在,我觉得uml很多时候还不如一个高真的axure更能传递清楚软件设计的信息

    展开全文
  • Yonge为Yonjee还是Yongay? 都不是,年轻。 同样,码头是关键。 哇。 我不是唯一一个为此而苦苦挣扎的人,所以我为来加拿大的游客,移民和有抱负的移民创造了这个漂亮的小扩展程序。 chrome扩展名可以帮助您立即...
  • Lists.partition(List<T> list, int size用法

    千次阅读 2019-12-04 11:33:20
    首先应该知道这个单词怎么读 使用partition()方法先引入jar <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version...
  • 假设一道包含n个单词的题,经蓝神过之后,虽然单词的长度和 数量都没有变化,但内容可能已经面目全非了。现在,蓝神的队友需要你帮忙计算一下原题与蓝神过之 后的题的相似度。单个单词相似度的计算方法是蓝神...
  • 习题1一个文件,包含英文句子,请统计共多少个不重复的单词,并且在另外一个文件中打印每个单词以及它的出线次数 #第一步:文件 #方法1:open #方法2:with #难点1:怎么把英文句子中的所有标点去掉。 #...
  • 目录操作

    2020-01-27 13:04:18
    一个文件,包含英文句子,请统计共多少个不重复并且在另外一个文件中打印每个单词及它出现的次数。 第一步:文件 方法1:open 方法2:with 难点:怎么把英文句子中的所有标点去掉。 数字也要替换掉。 import ...
  • 也就是单词加文件名的样子,那我们又怎么能知道map方法现在调用的这行数据属于哪个文件呢?调用这个方法的是mapworker,而且调用这个map方法的还不止一个worker。虽然调用map方法的有很多worker但是,每个worker再...
  • 2.2.9 A,B,C,D四个进程,A向buf里面写数据,B,C,D向buf里面数据,当A写完,且B,C,D都一次后,A才能再写。用P,V操作实现。 2.3.0 将单向链表reverse,如ABCD变成DCBA,只能搜索链表一次。 2.3.1 将二叉树的...
  • \ba\w*\b匹配以字母a开头的单词——先是某个单词开始处(\b),然后是字母a,然后是任意数量的字母或数字(\w*),最后是单词结束处(\b)。 好吧,现在我们说说正则表达式里的单词是什么意思吧:就是不少于一个的连续的\w...
  • # 上面从文件中到的目标句子是'X Y Z <eos>'的形式,我们需要从中生成'<sos> X Y Z'形式并加入到Dataset # 编码器只有输入,没有输出,而解码器有输入也有输出,输入为<sos>+(除去最后一位eos的label列表) # ...
  • Visual Studio程序员箴言--详细书签版

    热门讨论 2012-10-16 20:37:39
    技巧1.5 使用Ctrl+Delete组合键删除下一个单词,使用Ctrl+Backspace组合键删除前一个单词 3 技巧1.6 使用Ctrl+L组合键剪切当前行,使用Ctrl+Shift+L组合键删除当前行 4 技巧1.7 删除一行开头的水平空白 4 ...
  • 最新Java面试宝典pdf版

    热门讨论 2011-08-31 11:29:22
    1、编写一个程序,将a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt文件中,a.txt文件中的单词用回车符分隔,b.txt文件中用回车或空格进行分隔。 61 2、编写一个程序,将d:\java目录下的所有.java文件复制到d...
  • Java面试宝典-经典

    2015-03-28 21:44:36
    1、编写一个程序,将a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt文件中,a.txt文件中的单词用回车符分隔,b.txt文件中用回车或空格进行分隔。 61 2、编写一个程序,将d:\java目录下的所有.java文件复制到d...
  • Java面试宝典2010版

    2011-06-27 09:48:27
    1、编写一个程序,将a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt文件中,a.txt文件中的单词用回车符分隔,b.txt文件中用回车或空格进行分隔。 2、编写一个程序,将d:\java目录下的所有.java文件复制到d:\...
  • Java面试宝典2012版

    2012-12-03 21:57:42
    1、编写一个程序,将a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt文件中,a.txt文件中的单词用回车符分隔,b.txt文件中用回车或空格进行分隔。 61 2、编写一个程序,将d:\java目录下的所有.java文件复制到...
  • java面试宝典2012

    2012-12-16 20:43:41
    1、编写一个程序,将a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt文件中,a.txt文件中的单词用回车符分隔,b.txt文件中用回车或空格进行分隔。 67 2、编写一个程序,将d:\java目录下的所有.java文件复制到d...
  • 1、编写一个程序,将a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt文件中,a.txt文件中的单词用回车符分隔,b.txt文件中用回车或空格进行分隔。 61 2、编写一个程序,将d:\java目录下的所有.java文件复制到d...
  • Java面试笔试资料大全

    热门讨论 2011-07-22 14:33:56
    1、编写一个程序,将a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt文件中,a.txt文件中的单词用回车符分隔,b.txt文件中用回车或空格进行分隔。 61 2、编写一个程序,将d:\java目录下的所有.java文件复制到d...
  • Java面试宝典2012新版

    2012-06-26 19:20:00
    1、编写一个程序,将a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt文件中,a.txt文件中的单词用回车符分隔,b.txt文件中用回车或空格进行分隔。 61 2、编写一个程序,将d:\java目录下的所有.java文件复制到d...
  • JAVA面试宝典2010

    2011-12-20 16:13:24
    1、编写一个程序,将a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt文件中,a.txt文件中的单词用回车符分隔,b.txt文件中用回车或空格进行分隔。 61 2、编写一个程序,将d:\java目录下的所有.java文件复制到d...
  • 语言建模涉及开发一种统计模型,用于预测句子中的下一个单词或一个单词中的下一个单词。它是语音识别和机器翻译等任务中的前置任务。 它是语音识别和机器翻译等任务中的前置任务。 下面是一些很好的初学者语言建模...
  • 1、编写一个程序,将a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt文件中,a.txt文件中的单词用回车符分隔,b.txt文件中用回车或空格进行分隔。 61 2、编写一个程序,将d:\java目录下的所有.java文件复制到d...
  • 语言建模涉及开发一种统计模型,用于预测句子中的下一个单词或一个单词中的下一个单词。它是语音识别和机器翻译等任务中的前置任务。 它是语音识别和机器翻译等任务中的前置任务。 下面是一些很好的初学者语言建模...
  • 语言建模涉及开发一种统计模型,用于预测句子中的下一个单词或一个单词中的下一个单词。它是语音识别和机器翻译等任务中的前置任务。 它是语音识别和机器翻译等任务中的前置任务。 下面是一些很好的初学者语言建模...

空空如也

空空如也

1 2
收藏数 40
精华内容 16
关键字:

包单词怎么读