Indicium Search
There are many incredible search engines available for Rust. Many seem to
require compiling a separate server binary. I wanted something simple and
light-weight - an easy-to-use crate that could conveniently search structs and
collections within my own binary. So, I made indicium
.
While indicium
was made with web apps in mind, it is an in-memory search and
it does not scale indefinitely or to cloud size (i.e. Facebook or Google size).
Even in such an environment, it would still be a convenient way of searching
large lists (such as currencies, languages, countries, etc.) It's also great for
applications where there is an anticipated scale limit (i.e. searching a list of
company assets, list of users in a corporate intranet, etc.)
Indicium easily can handle millions of records without breaking a sweat thanks to Rust's BTreeMap. This crate is primarily limited by available memory. However, depending on the nature your data-set and if there keywords that are repeated many times, performance may begin to degrade at a point.
What's New?
-
0.5.0
: Performance improvements. Some functions will now return anIterator
rather than return aVec
to help improve efficiency. Breaking change, bumping version to0.5
. -
0.4.2
: Any type that implements ToString (and consequently any type that implements Display) now gets the Indexable implementation for free. -
0.4.1
: Improved contextual fuzzy matching. -
0.4.0
: Initial support for fuzzy searching. Fuzzy matching is applied to the last (partial) keyword in the search string for autocompletion and live search only. Keywords at the start or in the middle of the user's search string will not be substituted.- Some changes for an upcoming
0.5.0
release are being considered. This release could have some changes that would allowindicium
to provide feedback to the user, including which keywords have been substituted.
- Some changes for an upcoming
-
0.4.0
: Breaking changes:- Builder pattern is now passed owned values.
K
key type requiresHash
trait forfuzzy
string search feature.- New
SearchIndex
default settings.
-
0.4.0
: Any dependent software should see if (or how) the updated defaults change search behaviour and tweak accordingly before adopting the 0.4.0 update. -
0.3.7
: An experimental feature is now disabled by default to reduce resource consumption. -
0.3.6
: ImplementedDerefMut
which gives access to the search index's underlyingBTreeMap
. Implementedclear()
which is a convenience method for clearing the search index. -
0.3.5
: Peformance improvements. -
0.3.4
: Peformance improvements. -
0.3.3
: Fix:cargo test
failed. Sorry. -
0.3.2
: Fix: issue with search indexes that do not use keyword splitting. -
0.3.1
: Autocomplete no longer offers previously used keywords as options. -
0.3.1
: Addedmaximum_keys_per_keyword
getter method. -
0.3.1
: Addedautocomplete_with
andsearch_with
methods which allow ad-hoc overrides of theAutocompleteType
/SearchType
and maximum results parameters. -
0.3.0
: Added new search typeSearchType::Live
which is for "search as you type" interfaces. It is sort of a hybrid betweenautocomplete
andSearchType::And
. It will search using an (incomplete) string and return keys as the search results. Each resulting key can then be used to retrieve the full record from its collection to be rendered & displayed to the user.
Quick Start Guide
For our Quick Start Guide example, we will be searching inside of the
following struct
:
struct MyStruct {
title: String,
year: u16,
body: String,
}
1. Implementing Indexable
To begin, we must make our record indexable. We'll do this by implementing the
Indexable
trait for our struct
. The idea is to return a String
for every
field that we would like to be indexed. Example:
use indicium::simple::Indexable;
impl Indexable for MyStruct {
fn strings(&self) -> Vec<String> {
vec![
self.title.clone(),
self.year.to_string(),
self.body.clone(),
]
}
}
Don't forget that you may make numbers, numeric identifiers, enums, and other
types in your struct
(or other complex type) indexable by converting them to a
String
and including them in the returned Vec<String>
.
2. Indexing a Collection
To index an existing collection, we can iterate over the collection. For each record, we will insert it into the search index. This should look something like these two examples:
Vec
use indicium::simple::SearchIndex;
let my_vec: Vec<MyStruct> = Vec::new();
// In the case of a `Vec` collection, we use the index as our key. A
// `Vec` index is a `usize` type. Therefore we will instantiate
// `SearchIndex` as `SearchIndex<usize>`.
let mut search_index: SearchIndex<usize> = SearchIndex::default();
my_vec
.iter()
.enumerate()
.for_each(|(index, element)|
search_index.insert(&index, element)
);
HashMap
use std::collections::HashMap;
use indicium::simple::SearchIndex;
let my_hash_map: HashMap<String, MyStruct> = HashMap::new();
// In the case of a `HashMap` collection, we use the hash map's key as
// the `SearchIndex` key. In our hypothetical example, we will use
// MyStruct's `title` as a the key which is a `String` type. Therefore
// we will instantiate `HashMap<K, V>` as HashMap<String, MyStruct> and
// `SearchIndex<K>` as `SearchIndex<String>`.
let mut search_index: SearchIndex<String> = SearchIndex::default();
my_hash_map
.iter()
.for_each(|(key, value)|
search_index.insert(key, value)
);
As long as the Indexable
trait was implemented for your value type, the above
examples will index a previously populated Vec
or HashMap
. However, the
preferred method for large collections is to insert
into the SearchIndex
as
you insert into your collection (Vec, HashMap, etc.)
Once the index has been populated, you can use the search
and autocomplete
methods.
3. Searching
The search
method will return keys as the search results. Each resulting
key can then be used to retrieve the full record from its collection.
Basic usage:
let mut search_index: SearchIndex<usize> = SearchIndex::default();
search_index.insert(&0, &"Harold Godwinson");
search_index.insert(&1, &"Edgar Ætheling");
search_index.insert(&2, &"William the Conqueror");
search_index.insert(&3, &"William Rufus");
search_index.insert(&4, &"Henry Beauclerc");
let resulting_keys: Vec<&usize> = search_index.search("William");
assert_eq!(resulting_keys, vec![&2, &3]);
Search only supports exact keyword matches and does not use fuzzy matching.
Consider providing the autocomplete
feature to your users as an ergonomic
alternative to fuzzy matching.
5. Autocompletion
The autocomplete
method will provide several autocompletion options for the
last keyword in the supplied string.
Basic usage:
let mut search_index: SearchIndex<usize> =
SearchIndexBuilder::default()
.autocomplete_type(&AutocompleteType::Global)
.build();
search_index.insert(&0, &"apple");
search_index.insert(&1, &"ball");
search_index.insert(&3, &"bird");
search_index.insert(&4, &"birthday");
search_index.insert(&5, &"red");
let autocomplete_options: Vec<String> =
search_index.autocomplete("a very big bi");
assert_eq!(
autocomplete_options,
vec!["a very big bird", "a very big birthday"]
);