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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
use crate::simple::internal::string_keywords::SplitContext;
use crate::simple::search_index::SearchIndex;
use std::cmp::Ord;
// -----------------------------------------------------------------------------
impl<K: Ord> SearchIndex<K> {
// -------------------------------------------------------------------------
//
/// Returns matching autocompleted keywords for the provided search string.
/// _This search method accepts multiple keywords in the search string._
/// The last partial search keyword must be an exact match.
///
/// The search string may contain multiple keywords and the last (partial)
/// keyword will be autocompleted. The last keyword in the search string
/// will be autocompleted from all available keywords in the search index.
/// If your data-set is very large or has repetitive keywords, this is the
/// recommended autocomplete type. Results are returned in lexographic
/// order.
///
/// Basic usage:
///
/// ```ignore
/// # use indicium::simple::{AutocompleteType, Indexable, SearchIndex, SearchType};
/// #
/// # struct MyStruct {
/// # title: String,
/// # year: u16,
/// # body: String,
/// # }
/// #
/// # impl Indexable for MyStruct {
/// # fn strings(&self) -> Vec<String> {
/// # vec![
/// # self.title.clone(),
/// # self.year.to_string(),
/// # self.body.clone(),
/// # ]
/// # }
/// # }
/// #
/// # let my_vec = vec![
/// # MyStruct {
/// # title: "Harold Godwinson".to_string(),
/// # year: 1066,
/// # body: "Last crowned Anglo-Saxon king of England.".to_string(),
/// # },
/// # MyStruct {
/// # title: "Edgar Ætheling".to_string(),
/// # year: 1066,
/// # body: "Last male member of the royal house of Cerdic of Wessex.".to_string(),
/// # },
/// # MyStruct {
/// # title: "William the Conqueror".to_string(),
/// # year: 1066,
/// # body: "First Norman monarch of England.".to_string(),
/// # },
/// # MyStruct {
/// # title: "William Rufus".to_string(),
/// # year: 1087,
/// # body: "Third son of William the Conqueror.".to_string(),
/// # },
/// # MyStruct {
/// # title: "Henry Beauclerc".to_string(),
/// # year: 1100,
/// # body: "Fourth son of William the Conqueror.".to_string(),
/// # },
/// # ];
/// #
/// # let mut search_index: SearchIndex<usize> = SearchIndex::default();
/// #
/// # my_vec
/// # .iter()
/// # .enumerate()
/// # .for_each(|(index, element)|
/// # search_index.insert(&index, element)
/// # );
/// #
/// let autocomplete_options = search_index.autocomplete_global(
/// &5,
/// "1100 e"
/// );
///
/// assert_eq!(
/// autocomplete_options,
/// vec![
/// "1100 edgar".to_string(),
/// "1100 edgar ætheling".to_string(),
/// "1100 england".to_string()
/// ]
/// );
/// ```
#[tracing::instrument(level = "trace", name = "Global Autocomplete", skip(self))]
pub(crate) fn autocomplete_global(
&self,
maximum_autocomplete_options: &usize,
string: &str,
) -> Vec<String> {
// Split search `String` into keywords according to the `SearchIndex`
// settings. Force "use entire string as a keyword" option off:
let mut keywords: Vec<String> = self.string_keywords(
string,
SplitContext::Searching,
);
// For debug builds:
#[cfg(debug_assertions)]
tracing::trace!("Autocompleting: {:?}", keywords);
// Pop the last keyword off the list. It's the keyword that we'll be
// autocompleting:
if let Some(last_keyword) = keywords.pop() {
// Autocomplete the last keyword:
let autocompletions: Vec<&String> = self.b_tree_map
// Get matching keywords starting with (partial) keyword string:
.range(last_keyword.to_string()..)
// `range` returns a key-value pair. We're autocompleting the
// key (keyword), so discard the value (record key):
.map(|(key, _value)| key)
// We did not specify an end bound for our `range` function (see
// above.) `range` will return _every_ keyword greater than the
// supplied keyword. The below `take_while` will effectively
// break iteration when we reach a keyword that does not start
// with our supplied (partial) keyword.
.take_while(|autocompletion| autocompletion.starts_with(&last_keyword))
// If the index's keyword matches the user's keyword, don't
// return it as a result. For example, if the user's keyword was
// "new" (as in New York), do not return "new" as an
// auto-completed keyword:
// .filter(|autocompletion| *autocompletion != &last_keyword)
// Only keep this autocompletion if hasn't already been used as
// a keyword:
.filter(|autocompletion| !keywords.contains(autocompletion))
// If the index's keyword matches the user's keyword, don't
// return it as a result. For example, if the user's keyword was
// "new" (as in New York), do not return "new" as an
// auto-completed keyword:
// .filter(|autocompletion| *autocompletion != &keyword)
// Only return `maximum_autocomplete_options` number of
// keywords:
.take(*maximum_autocomplete_options)
// Collect all keyword autocompletions into a `Vec`:
.collect();
// Push a blank placeholder onto the end of the keyword list. We
// will be putting our autocompletions for the last keyword into
// this spot:
keywords.push("".to_string());
// Build autocompleted search strings from the autocompletions
// derived from the last keyword:
autocompletions
// Iterate over each autocompleted last keyword:
.iter()
// Use the prepended `keywords` and autocompleted last keyword
// to build an autocompleted search string:
.map(|autocompletion| {
// Remove previous autocompleted last keyword from list:
keywords.pop();
// Add current autocompleted last keyword to end of list:
keywords.push(autocompletion.to_string());
// Join all keywords together into a single `String` using a
// space delimiter:
keywords.join(" ")
})
// Collect all string autocompletions into a `Vec`:
.collect()
} else {
// The search string did not have a last keyword to autocomplete.
// Return an empty `Vec`:
Vec::new()
} // if
} // fn
} // impl