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
use crate::simple::internal::string_keywords::SplitContext;
use std::{collections::BTreeSet, hash::Hash};
// -----------------------------------------------------------------------------
impl<K: Hash + Ord> crate::simple::search_index::SearchIndex<K> {
/// This search function will return keys as the search results. Each
/// resulting key can then be used to retrieve the full record from its
/// collection. _This search method accepts multiple keywords in the search
/// string._ Search keywords must be an exact match.
///
/// With this search type, the logical conjuction for multiple keywords is
/// `And`. For example, a search of `this that` will only return records
/// containing keywords both `this` and `that`. In other words, _all_
/// keywords must be present in a record for it to be returned as a result.
///
/// For this search, the results are returned in lexographic order. This
/// conjuction uses less CPU resources than `Or`.
///
/// The `And` search feels more like "use my keywords to filter out the
/// records I don't want." It's also a better choice for large collections
/// because it uses less CPU resouces than `Or`.
///
/// 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.
///
/// Basic usage:
///
/// ```ignore
/// # use indicium::simple::{AutocompleteType, Indexable, SearchIndex, SearchType};
/// # use pretty_assertions::assert_eq;
/// #
/// # 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 search_results = search_index.and_search(&20, "Conqueror third");
/// assert_eq!(search_results, vec![&3]);
/// ```
#[tracing::instrument(level = "trace", name = "and search", skip(self))]
pub(crate) fn and_search(
&self,
maximum_search_results: usize,
string: &str
) -> Vec<&K> {
// Split search `String` into keywords (according to the `SearchIndex`
// settings). `string_keywords` will **not** allow "use entire string as
// a keyword," even if enabled in user settings:
let keywords: Vec<kstring::KString> =
self.string_keywords(string, &SplitContext::Searching);
// For debug builds:
#[cfg(debug_assertions)]
tracing::debug!("searching: {:?}", keywords);
// This `BTreeSet` is used to contain the search results:
let mut search_results = BTreeSet::<&K>::new();
// Get each keyword from our `BTreeMap`, and intersect the resulting
// keys with our current keys:
for keyword in keywords {
// Attempt to retrieve keyword from search index. If keyword
// found, intersect keyword records with search results records.
// If keyword not found, empty search results:
if let Some(keyword_results) = self.b_tree_map.get(&keyword) {
// Check if `search_results` is already populated:
search_results = if search_results.is_empty() {
self
.internal_keyword_search(&keyword)
.collect()
} else {
search_results
// Iterate over each search result record:
.into_iter()
// Intersect the search result record with the
// keyword results. If the search result record
// doesn't exist in this keyword's results, filter
// it out:
.filter(|key| keyword_results.contains(key))
// And collect each key into a `BTreeSet` that will
// become the new `search_results`:
.collect()
} // if
} else {
search_results.clear();
return search_results
.into_iter()
.take(maximum_search_results)
.collect()
} // if
} // for_each
// Return search results:
search_results
.into_iter()
.take(maximum_search_results)
.collect()
} // fn
} // impl