├── .gitignore ├── src ├── lib.rs ├── tokenizer.rs └── token_stream.rs ├── .github └── workflows │ └── test.yml ├── README.md ├── Cargo.toml └── examples └── basic.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod tokenizer; 2 | pub mod token_stream; 3 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Build 13 | run: cargo build --verbose 14 | - name: Run tests 15 | run: cargo test --verbose 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tantivy-tokenizer-tiny-segmenter 2 | ================================ 3 | 4 | A Japanese tokenizer for [Tantivy](https://github.com/tantivy-search/tantivy) based on [TinySegmenter](http://chasen.org/~taku/software/TinySegmenter/). Compatible with Tantivy 0.10. 5 | 6 | See examples/basic.rs for basic usage. 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tantivy-tokenizer-tiny-segmenter" 3 | version = "0.3.0" 4 | authors = ["osyoyu "] 5 | edition = "2018" 6 | description = "A Japanese tokenizer for Tantivy, based on TinySegmenter." 7 | repository = "https://github.com/osyoyu/tantivy-tokenizer-tiny-segmenter" 8 | readme = "README.md" 9 | keywords = ["tantivy", "tokenizer", "japanese"] 10 | license = "MIT" 11 | 12 | [dependencies] 13 | tantivy = "0.10.2" 14 | tinysegmenter = "0.1.1" 15 | -------------------------------------------------------------------------------- /src/tokenizer.rs: -------------------------------------------------------------------------------- 1 | use tantivy::tokenizer::Tokenizer; 2 | 3 | use crate::token_stream::TinySegmenterTokenStream; 4 | 5 | /// Tokenizer for Japanese text, based on TinySegmenter. 6 | #[derive(Clone)] 7 | pub struct TinySegmenterTokenizer; 8 | 9 | impl<'a> Tokenizer<'a> for TinySegmenterTokenizer { 10 | type TokenStreamImpl = TinySegmenterTokenStream; 11 | 12 | fn token_stream(&self, text: &'a str) -> Self::TokenStreamImpl { 13 | TinySegmenterTokenStream::new(text) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/token_stream.rs: -------------------------------------------------------------------------------- 1 | use std::iter::Enumerate; 2 | use tantivy::tokenizer::{Token, TokenStream}; 3 | 4 | pub struct TinySegmenterTokenStream { 5 | tinyseg_enum: Enumerate>, 6 | current_token: Token, 7 | offset_from: usize, 8 | offset_to: usize, 9 | } 10 | 11 | impl TinySegmenterTokenStream { 12 | pub fn new(text: &str) -> TinySegmenterTokenStream { 13 | TinySegmenterTokenStream { 14 | tinyseg_enum: tinysegmenter::tokenize(text).into_iter().enumerate(), 15 | current_token: Token::default(), 16 | offset_from: 0, 17 | offset_to: 0, 18 | } 19 | } 20 | } 21 | 22 | impl TokenStream for TinySegmenterTokenStream { 23 | fn advance(&mut self) -> bool { 24 | match self.tinyseg_enum.next() { 25 | Some((pos, term)) => { 26 | self.offset_from = self.offset_to; 27 | self.offset_to = self.offset_from + term.len(); 28 | 29 | let offset_from = self.offset_from; 30 | let offset_to = self.offset_to; 31 | 32 | self.current_token = Token { 33 | offset_from, 34 | offset_to, 35 | position: pos, 36 | text: term, 37 | position_length: 1, 38 | }; 39 | 40 | return true; 41 | } 42 | 43 | None => return false, 44 | } 45 | } 46 | 47 | fn token(&self) -> &Token { 48 | &self.current_token 49 | } 50 | 51 | fn token_mut(&mut self) -> &mut Token { 52 | &mut self.current_token 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/basic.rs: -------------------------------------------------------------------------------- 1 | // # Defining a tokenizer pipeline 2 | // 3 | // In this example, we'll see how to define a tokenizer pipeline 4 | // by aligning a bunch of `TokenFilter`. 5 | use tantivy::collector::TopDocs; 6 | use tantivy::query::QueryParser; 7 | use tantivy::schema::*; 8 | use tantivy::{doc, Index}; 9 | use tantivy_tokenizer_tiny_segmenter::tokenizer::TinySegmenterTokenizer; 10 | fn main() -> tantivy::Result<()> { 11 | // # Defining the schema 12 | // 13 | // The Tantivy index requires a very strict schema. 14 | // The schema declares which fields are in the index, 15 | // and for each field, its type and "the way it should 16 | // be indexed". 17 | 18 | // first we need to define a schema ... 19 | let mut schema_builder = Schema::builder(); 20 | 21 | // Create a new field `body` using TinySegmenter as the tokenizer. 22 | let text_field_indexing = TextFieldIndexing::default() 23 | .set_tokenizer("tinyseg") 24 | .set_index_option(IndexRecordOption::WithFreqsAndPositions); 25 | let text_options = TextOptions::default() 26 | .set_indexing_options(text_field_indexing) 27 | .set_stored(); 28 | let body = schema_builder.add_text_field("body", text_options); 29 | 30 | let schema = schema_builder.build(); 31 | 32 | // # Indexing documents 33 | // 34 | // Let's create a brand new index. 35 | // To simplify we will work entirely in RAM. 36 | // This is not what you want in reality, but it is very useful 37 | // for your unit tests... Or this example. 38 | let index = Index::create_in_ram(schema.clone()); 39 | 40 | // Register TinySegmenterTokenizer as "tinyseg". 41 | index 42 | .tokenizers() 43 | .register("tinyseg", TinySegmenterTokenizer {}); 44 | 45 | // To insert document we need an index writer. 46 | // There must be only one writer at a time. 47 | // This single `IndexWriter` is already 48 | // multithreaded. 49 | // 50 | // Here we use a buffer of 50MB per thread. Using a bigger 51 | // heap for the indexer can increase its throughput. 52 | let mut index_writer = index.writer(50_000_000)?; 53 | index_writer.add_document(doc!( 54 | body => "日本語の本文", 55 | )); 56 | index_writer.add_document(doc!( 57 | body => r#"「この早起きというのは」と、彼は思った、「人間をまったく薄ばかにしてしまうのだ。人間は眠りをもたなければならない。 58 | ほかのセールスマンたちはまるでハレムの女たちのような生活をしている。たとえばおれがまだ午前中に宿へもどってきて、 59 | 取ってきた注文を書きとめようとすると、やっとあの連中は朝食のテーブルについているところだ。 60 | そんなことをやったらおれの店主がなんていうか、見たいものだ。おれはすぐさまくびになってしまうだろう。"#, 61 | )); 62 | index_writer.add_document(doc!( 63 | body => r#"吾輩は猫である。名前はまだ無い。 64 | どこで生れたかとんと見当がつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。 65 | 吾輩はここで始めて人間というものを見た。しかもあとで聞くとそれは書生という人間中で一番獰悪な種族であったそうだ。 66 | この書生というのは時々我々を捕えて煮て食うという話である。しかしその当時は何という考もなかったから別段恐しいとも思わなかった。 67 | ただ彼の掌に載せられてスーと持ち上げられた時何だかフワフワした感じがあったばかりである。 68 | 掌の上で少し落ちついて書生の顔を見たのがいわゆる人間というものの見始であろう。この時妙なものだと思った感じが今でも残っている。 69 | 第一毛をもって装飾されべきはずの顔がつるつるしてまるで薬缶だ。その後猫にもだいぶ逢ったがこんな片輪には一度も出会わした事がない。 70 | のみならず顔の真中があまりに突起している。そうしてその穴の中から時々ぷうぷうと煙を吹く。 71 | どうも咽せぽくて実に弱った。これが人間の飲む煙草というものである事はようやくこの頃知った。"#, 72 | )); 73 | index_writer.commit()?; 74 | 75 | let reader = index.reader()?; 76 | let searcher = reader.searcher(); 77 | 78 | // The query parser can interpret human queries. 79 | // Here, if the user does not specify which 80 | // field they want to search, tantivy will search 81 | // in both title and body. 82 | let query_parser = QueryParser::for_index(&index, vec![body]); 83 | 84 | // Search for "人間", which is contained in the 2nd and 3rd document. 85 | let query = query_parser.parse_query("人間")?; 86 | 87 | let top_docs = searcher.search(&query, &TopDocs::with_limit(10))?; 88 | 89 | for (_, doc_address) in top_docs { 90 | let retrieved_doc = searcher.doc(doc_address)?; 91 | println!("{}", schema.to_json(&retrieved_doc)); 92 | } 93 | 94 | Ok(()) 95 | } 96 | --------------------------------------------------------------------------------