From 039a9a4cc7463e6c18aed3c78c622b8bbb38b51d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= <renault.cle@gmail.com>
Date: Sun, 6 Jan 2019 11:11:55 +0100
Subject: [PATCH 1/7] feat: Pre-compute matches query index groups

---
 src/rank/criterion/exact.rs                  |  7 +-
 src/rank/criterion/number_of_words.rs        |  8 +--
 src/rank/criterion/sum_of_typos.rs           | 11 ++--
 src/rank/criterion/sum_of_words_attribute.rs | 10 ++-
 src/rank/criterion/sum_of_words_position.rs  | 12 ++--
 src/rank/criterion/words_proximity.rs        |  8 +--
 src/rank/mod.rs                              | 69 ++++++++++++++++++--
 src/rank/query_builder.rs                    |  2 +-
 8 files changed, 86 insertions(+), 41 deletions(-)

diff --git a/src/rank/criterion/exact.rs b/src/rank/criterion/exact.rs
index 041a6ae67..6d33a6f38 100644
--- a/src/rank/criterion/exact.rs
+++ b/src/rank/criterion/exact.rs
@@ -2,9 +2,8 @@ use std::cmp::Ordering;
 use std::ops::Deref;
 
 use rocksdb::DB;
-use group_by::GroupBy;
 
-use crate::rank::{match_query_index, Document};
+use crate::rank::{Document, Matches};
 use crate::rank::criterion::Criterion;
 use crate::database::DatabaseView;
 use crate::Match;
@@ -15,8 +14,8 @@ fn contains_exact(matches: &[Match]) -> bool {
 }
 
 #[inline]
-fn number_exact_matches(matches: &[Match]) -> usize {
-    GroupBy::new(matches, match_query_index).map(contains_exact).count()
+fn number_exact_matches(matches: &Matches) -> usize {
+    matches.query_index_groups().map(contains_exact).count()
 }
 
 #[derive(Debug, Clone, Copy)]
diff --git a/src/rank/criterion/number_of_words.rs b/src/rank/criterion/number_of_words.rs
index 855d997ba..23cf36a2c 100644
--- a/src/rank/criterion/number_of_words.rs
+++ b/src/rank/criterion/number_of_words.rs
@@ -2,16 +2,14 @@ use std::cmp::Ordering;
 use std::ops::Deref;
 
 use rocksdb::DB;
-use group_by::GroupBy;
 
-use crate::rank::{match_query_index, Document};
+use crate::rank::{Document, Matches};
 use crate::rank::criterion::Criterion;
 use crate::database::DatabaseView;
-use crate::Match;
 
 #[inline]
-fn number_of_query_words(matches: &[Match]) -> usize {
-    GroupBy::new(matches, match_query_index).count()
+fn number_of_query_words(matches: &Matches) -> usize {
+    matches.query_index_groups().count()
 }
 
 #[derive(Debug, Clone, Copy)]
diff --git a/src/rank/criterion/sum_of_typos.rs b/src/rank/criterion/sum_of_typos.rs
index 409c37cb4..18a199113 100644
--- a/src/rank/criterion/sum_of_typos.rs
+++ b/src/rank/criterion/sum_of_typos.rs
@@ -3,21 +3,18 @@ use std::ops::Deref;
 
 use rocksdb::DB;
 
-use group_by::GroupBy;
-
-use crate::rank::{match_query_index, Document};
+use crate::rank::{Document, Matches};
 use crate::rank::criterion::Criterion;
 use crate::database::DatabaseView;
-use crate::Match;
 
 #[inline]
-fn sum_matches_typos(matches: &[Match]) -> i8 {
+fn sum_matches_typos(matches: &Matches) -> i8 {
     let mut sum_typos = 0;
     let mut number_words = 0;
 
     // note that GroupBy will never return an empty group
     // so we can do this assumption safely
-    for group in GroupBy::new(matches, match_query_index) {
+    for group in matches.query_index_groups() {
         sum_typos += unsafe { group.get_unchecked(0).distance } as i8;
         number_words += 1;
     }
@@ -44,7 +41,7 @@ where D: Deref<Target=DB>
 mod tests {
     use super::*;
 
-    use crate::{DocumentId, Attribute, WordArea};
+    use crate::{Match, DocumentId, Attribute, WordArea};
 
     // typing: "Geox CEO"
     //
diff --git a/src/rank/criterion/sum_of_words_attribute.rs b/src/rank/criterion/sum_of_words_attribute.rs
index 718ae7447..cfcaaec68 100644
--- a/src/rank/criterion/sum_of_words_attribute.rs
+++ b/src/rank/criterion/sum_of_words_attribute.rs
@@ -2,19 +2,17 @@ use std::cmp::Ordering;
 use std::ops::Deref;
 
 use rocksdb::DB;
-use group_by::GroupBy;
 
+use crate::rank::{Document, Matches};
 use crate::database::DatabaseView;
-use crate::rank::{match_query_index, Document};
 use crate::rank::criterion::Criterion;
-use crate::Match;
 
 #[inline]
-fn sum_matches_attributes(matches: &[Match]) -> u16 {
+fn sum_matches_attributes(matches: &Matches) -> u16 {
     // note that GroupBy will never return an empty group
     // so we can do this assumption safely
-    GroupBy::new(matches, match_query_index).map(|group| unsafe {
-        group.get_unchecked(0).attribute.attribute()
+    matches.query_index_groups().map(|group| {
+        unsafe { group.get_unchecked(0).attribute.attribute() }
     }).sum()
 }
 
diff --git a/src/rank/criterion/sum_of_words_position.rs b/src/rank/criterion/sum_of_words_position.rs
index d0ebaa74f..e1d650534 100644
--- a/src/rank/criterion/sum_of_words_position.rs
+++ b/src/rank/criterion/sum_of_words_position.rs
@@ -2,19 +2,17 @@ use std::cmp::Ordering;
 use std::ops::Deref;
 
 use rocksdb::DB;
-use group_by::GroupBy;
 
-use crate::database::DatabaseView;
-use crate::rank::{match_query_index, Document};
+use crate::rank::{Document, Matches};
 use crate::rank::criterion::Criterion;
-use crate::Match;
+use crate::database::DatabaseView;
 
 #[inline]
-fn sum_matches_attribute_index(matches: &[Match]) -> u32 {
+fn sum_matches_attribute_index(matches: &Matches) -> u32 {
     // note that GroupBy will never return an empty group
     // so we can do this assumption safely
-    GroupBy::new(matches, match_query_index).map(|group| unsafe {
-        group.get_unchecked(0).attribute.word_index()
+    matches.query_index_groups().map(|group| {
+        unsafe { group.get_unchecked(0).attribute.word_index() }
     }).sum()
 }
 
diff --git a/src/rank/criterion/words_proximity.rs b/src/rank/criterion/words_proximity.rs
index fc80dfaec..41f4b49b8 100644
--- a/src/rank/criterion/words_proximity.rs
+++ b/src/rank/criterion/words_proximity.rs
@@ -2,9 +2,8 @@ use std::cmp::{self, Ordering};
 use std::ops::Deref;
 
 use rocksdb::DB;
-use group_by::GroupBy;
 
-use crate::rank::{match_query_index, Document};
+use crate::rank::{Document, Matches};
 use crate::rank::criterion::Criterion;
 use crate::database::DatabaseView;
 use crate::Match;
@@ -34,9 +33,9 @@ fn min_proximity(lhs: &[Match], rhs: &[Match]) -> u32 {
     min_prox
 }
 
-fn matches_proximity(matches: &[Match]) -> u32 {
+fn matches_proximity(matches: &Matches) -> u32 {
     let mut proximity = 0;
-    let mut iter = GroupBy::new(matches, match_query_index);
+    let mut iter = matches.query_index_groups();
 
     // iterate over groups by windows of size 2
     let mut last = iter.next();
@@ -91,6 +90,7 @@ mod tests {
         //   soup -> of = 8
         // + of -> the  = 1
         // + the -> day = 8 (not 1)
+        let matches = Matches::from_unsorted_matches(matches.to_vec());
         assert_eq!(matches_proximity(matches), 17);
     }
 
diff --git a/src/rank/mod.rs b/src/rank/mod.rs
index 4d1b6b1ea..0d3f538d0 100644
--- a/src/rank/mod.rs
+++ b/src/rank/mod.rs
@@ -2,6 +2,11 @@ pub mod criterion;
 mod query_builder;
 mod distinct_map;
 
+use std::slice::Windows;
+
+use sdset::SetBuf;
+use group_by::GroupBy;
+
 use crate::{Match, DocumentId};
 
 pub use self::query_builder::{FilterFunc, QueryBuilder, DistinctQueryBuilder};
@@ -14,20 +19,70 @@ fn match_query_index(a: &Match, b: &Match) -> bool {
 #[derive(Debug, Clone)]
 pub struct Document {
     pub id: DocumentId,
-    pub matches: Vec<Match>,
+    pub matches: Matches,
 }
 
 impl Document {
     pub fn new(doc: DocumentId, match_: Match) -> Self {
-        unsafe { Self::from_sorted_matches(doc, vec![match_]) }
+        let matches = SetBuf::new_unchecked(vec![match_]);
+        Self::from_matches(doc, matches)
     }
 
-    pub fn from_matches(doc: DocumentId, mut matches: Vec<Match>) -> Self {
-        matches.sort_unstable();
-        unsafe { Self::from_sorted_matches(doc, matches) }
-    }
+    pub fn from_matches(id: DocumentId, matches: SetBuf<Match>) -> Self {
+        let mut last = 0;
+        let mut slices = vec![0];
+        for group in GroupBy::new(&matches, match_query_index) {
+            let index = last + group.len();
+            slices.push(index);
+            last = index;
+        }
 
-    pub unsafe fn from_sorted_matches(id: DocumentId, matches: Vec<Match>) -> Self {
+        let matches = Matches { matches, slices };
         Self { id, matches }
     }
+
+    pub fn from_unsorted_matches(doc: DocumentId, mut matches: Vec<Match>) -> Self {
+        matches.sort_unstable();
+        let matches = SetBuf::new_unchecked(matches);
+        Self::from_matches(doc, matches)
+    }
 }
+
+#[derive(Debug, Clone)]
+pub struct Matches {
+    matches: SetBuf<Match>,
+    slices: Vec<usize>,
+}
+
+impl Matches {
+    pub fn query_index_groups(&self) -> QueryIndexGroups {
+        QueryIndexGroups {
+            matches: &self.matches,
+            windows: self.slices.windows(2),
+        }
+    }
+}
+
+pub struct QueryIndexGroups<'a, 'b> {
+    matches: &'a [Match],
+    windows: Windows<'b, usize>,
+}
+
+impl<'a, 'b> Iterator for QueryIndexGroups<'a, 'b> {
+    type Item = &'a [Match];
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.windows.next().map(|range| {
+            match *range {
+                [left, right] => &self.matches[left..right],
+                _             => unreachable!()
+            }
+        })
+    }
+}
+
+// impl ExactSizeIterator for QueryIndexGroups<'_, '_> {
+//     fn len(&self) -> usize {
+//         self.windows.len() // FIXME (+1) ?
+//     }
+// }
diff --git a/src/rank/query_builder.rs b/src/rank/query_builder.rs
index 4df983f8a..9a17e0473 100644
--- a/src/rank/query_builder.rs
+++ b/src/rank/query_builder.rs
@@ -116,7 +116,7 @@ where D: Deref<Target=DB>,
             }
         }
 
-        matches.into_iter().map(|(id, matches)| Document::from_matches(id, matches)).collect()
+        matches.into_iter().map(|(id, m)| Document::from_unsorted_matches(id, m)).collect()
     }
 }
 

From d21406a93908b2b3ed8cd57b2182996b43cf7806 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= <renault.cle@gmail.com>
Date: Sun, 6 Jan 2019 11:23:21 +0100
Subject: [PATCH 2/7] feat: Allow Matches to be constructed

---
 src/rank/criterion/sum_of_typos.rs    | 30 ++++---------------
 src/rank/criterion/words_proximity.rs |  9 ++++--
 src/rank/mod.rs                       | 42 +++++++++++++++++----------
 3 files changed, 38 insertions(+), 43 deletions(-)

diff --git a/src/rank/criterion/sum_of_typos.rs b/src/rank/criterion/sum_of_typos.rs
index 18a199113..400650ad2 100644
--- a/src/rank/criterion/sum_of_typos.rs
+++ b/src/rank/criterion/sum_of_typos.rs
@@ -66,10 +66,7 @@ mod tests {
                     word_area: WordArea::new_faillible(0, 6)
                 },
             ];
-            Document {
-                id: DocumentId(0),
-                matches: matches,
-            }
+            Document::from_unsorted_matches(DocumentId(0), matches)
         };
 
         let doc1 = {
@@ -89,10 +86,7 @@ mod tests {
                     word_area: WordArea::new_faillible(0, 6)
                 },
             ];
-            Document {
-                id: DocumentId(1),
-                matches: matches,
-            }
+            Document::from_unsorted_matches(DocumentId(1), matches)
         };
 
         let lhs = sum_matches_typos(&doc0.matches);
@@ -123,10 +117,7 @@ mod tests {
                     word_area: WordArea::new_faillible(0, 6)
                 },
             ];
-            Document {
-                id: DocumentId(0),
-                matches: matches,
-            }
+            Document::from_unsorted_matches(DocumentId(0), matches)
         };
 
         let doc1 = {
@@ -139,10 +130,7 @@ mod tests {
                     word_area: WordArea::new_faillible(0, 6)
                 },
             ];
-            Document {
-                id: DocumentId(1),
-                matches: matches,
-            }
+            Document::from_unsorted_matches(DocumentId(1), matches)
         };
 
         let lhs = sum_matches_typos(&doc0.matches);
@@ -173,10 +161,7 @@ mod tests {
                     word_area: WordArea::new_faillible(0, 6)
                 },
             ];
-            Document {
-                id: DocumentId(0),
-                matches: matches,
-            }
+            Document::from_unsorted_matches(DocumentId(0), matches)
         };
 
         let doc1 = {
@@ -189,10 +174,7 @@ mod tests {
                     word_area: WordArea::new_faillible(0, 6)
                 },
             ];
-            Document {
-                id: DocumentId(1),
-                matches: matches,
-            }
+            Document::from_unsorted_matches(DocumentId(1), matches)
         };
 
         let lhs = sum_matches_typos(&doc0.matches);
diff --git a/src/rank/criterion/words_proximity.rs b/src/rank/criterion/words_proximity.rs
index 41f4b49b8..7fe3102d3 100644
--- a/src/rank/criterion/words_proximity.rs
+++ b/src/rank/criterion/words_proximity.rs
@@ -90,8 +90,8 @@ mod tests {
         //   soup -> of = 8
         // + of -> the  = 1
         // + the -> day = 8 (not 1)
-        let matches = Matches::from_unsorted_matches(matches.to_vec());
-        assert_eq!(matches_proximity(matches), 17);
+        let matches = Matches::from_unsorted(matches.to_vec());
+        assert_eq!(matches_proximity(&matches), 17);
     }
 
     #[test]
@@ -118,7 +118,8 @@ mod tests {
         //   soup -> of = 1
         // + of -> the  = 1
         // + the -> day = 1
-        assert_eq!(matches_proximity(matches), 3);
+        let matches = Matches::from_unsorted(matches.to_vec());
+        assert_eq!(matches_proximity(&matches), 3);
     }
 }
 
@@ -152,6 +153,8 @@ mod bench {
             matches.push(match_);
         }
 
+        let matches = Matches::from_unsorted(matches.to_vec());
+
         bench.iter(|| {
             let proximity = matches_proximity(&matches);
             test::black_box(move || proximity)
diff --git a/src/rank/mod.rs b/src/rank/mod.rs
index 0d3f538d0..ad91830bf 100644
--- a/src/rank/mod.rs
+++ b/src/rank/mod.rs
@@ -23,28 +23,19 @@ pub struct Document {
 }
 
 impl Document {
-    pub fn new(doc: DocumentId, match_: Match) -> Self {
+    pub fn new(id: DocumentId, match_: Match) -> Self {
         let matches = SetBuf::new_unchecked(vec![match_]);
-        Self::from_matches(doc, matches)
+        Self::from_matches(id, matches)
     }
 
     pub fn from_matches(id: DocumentId, matches: SetBuf<Match>) -> Self {
-        let mut last = 0;
-        let mut slices = vec![0];
-        for group in GroupBy::new(&matches, match_query_index) {
-            let index = last + group.len();
-            slices.push(index);
-            last = index;
-        }
-
-        let matches = Matches { matches, slices };
+        let matches = Matches::new(matches);
         Self { id, matches }
     }
 
-    pub fn from_unsorted_matches(doc: DocumentId, mut matches: Vec<Match>) -> Self {
-        matches.sort_unstable();
-        let matches = SetBuf::new_unchecked(matches);
-        Self::from_matches(doc, matches)
+    pub fn from_unsorted_matches(id: DocumentId, matches: Vec<Match>) -> Self {
+        let matches = Matches::from_unsorted(matches);
+        Self { id, matches }
     }
 }
 
@@ -55,6 +46,25 @@ pub struct Matches {
 }
 
 impl Matches {
+    pub fn new(matches: SetBuf<Match>) -> Matches {
+        let mut last = 0;
+        let mut slices = vec![0];
+
+        for group in GroupBy::new(&matches, match_query_index) {
+            let index = last + group.len();
+            slices.push(index);
+            last = index;
+        }
+
+        Matches { matches, slices }
+    }
+
+    pub fn from_unsorted(mut matches: Vec<Match>) -> Matches {
+        matches.sort_unstable();
+        let matches = SetBuf::new_unchecked(matches);
+        Matches::new(matches)
+    }
+
     pub fn query_index_groups(&self) -> QueryIndexGroups {
         QueryIndexGroups {
             matches: &self.matches,
@@ -75,7 +85,7 @@ impl<'a, 'b> Iterator for QueryIndexGroups<'a, 'b> {
         self.windows.next().map(|range| {
             match *range {
                 [left, right] => &self.matches[left..right],
-                _             => unreachable!()
+                _             => unreachable!(),
             }
         })
     }

From ef7ba96d4a3f2107c386aa33860e707b812ab732 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= <renault.cle@gmail.com>
Date: Sun, 6 Jan 2019 11:23:42 +0100
Subject: [PATCH 3/7] feat: Introducing the Matches as_matches method

---
 examples/query-database.rs | 4 ++--
 src/rank/mod.rs            | 4 ++++
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/examples/query-database.rs b/examples/query-database.rs
index ce64870a9..edd311f16 100644
--- a/examples/query-database.rs
+++ b/examples/query-database.rs
@@ -115,7 +115,7 @@ fn main() -> Result<(), Box<Error>> {
                         };
 
                         print!("{}: ", name);
-                        let areas = create_highlight_areas(&text, &doc.matches, attr);
+                        let areas = create_highlight_areas(&text, doc.matches.as_matches(), attr);
                         display_highlights(&text, &areas)?;
                         println!();
                     }
@@ -124,7 +124,7 @@ fn main() -> Result<(), Box<Error>> {
             }
 
             let mut matching_attributes = HashSet::new();
-            for _match in doc.matches {
+            for _match in doc.matches.as_matches() {
                 let attr = SchemaAttr::new(_match.attribute.attribute());
                 let name = schema.attribute_name(attr);
                 matching_attributes.insert(name);
diff --git a/src/rank/mod.rs b/src/rank/mod.rs
index ad91830bf..5416ca882 100644
--- a/src/rank/mod.rs
+++ b/src/rank/mod.rs
@@ -71,6 +71,10 @@ impl Matches {
             windows: self.slices.windows(2),
         }
     }
+
+    pub fn as_matches(&self) -> &[Match] {
+        &self.matches
+    }
 }
 
 pub struct QueryIndexGroups<'a, 'b> {

From c594597a01422087a66e78bdfddd32d5abc99ad2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= <renault.cle@gmail.com>
Date: Sun, 6 Jan 2019 11:29:43 +0100
Subject: [PATCH 4/7] feat: Introduce multiple Iterator impl for Matches

---
 src/rank/mod.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 54 insertions(+), 6 deletions(-)

diff --git a/src/rank/mod.rs b/src/rank/mod.rs
index 5416ca882..2023cb266 100644
--- a/src/rank/mod.rs
+++ b/src/rank/mod.rs
@@ -2,6 +2,7 @@ pub mod criterion;
 mod query_builder;
 mod distinct_map;
 
+use std::iter::FusedIterator;
 use std::slice::Windows;
 
 use sdset::SetBuf;
@@ -82,9 +83,10 @@ pub struct QueryIndexGroups<'a, 'b> {
     windows: Windows<'b, usize>,
 }
 
-impl<'a, 'b> Iterator for QueryIndexGroups<'a, 'b> {
+impl<'a> Iterator for QueryIndexGroups<'a, '_> {
     type Item = &'a [Match];
 
+    #[inline]
     fn next(&mut self) -> Option<Self::Item> {
         self.windows.next().map(|range| {
             match *range {
@@ -93,10 +95,56 @@ impl<'a, 'b> Iterator for QueryIndexGroups<'a, 'b> {
             }
         })
     }
+
+    #[inline]
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.windows.size_hint()
+    }
+
+    #[inline]
+    fn count(self) -> usize {
+        self.len()
+    }
+
+    #[inline]
+    fn nth(&mut self, n: usize) -> Option<Self::Item> {
+        self.windows.nth(n).map(|range| {
+            match *range {
+                [left, right] => &self.matches[left..right],
+                _             => unreachable!(),
+            }
+        })
+    }
+
+    #[inline]
+    fn last(self) -> Option<Self::Item> {
+        let (matches, windows) = (self.matches, self.windows);
+        windows.last().map(|range| {
+            match *range {
+                [left, right] => &matches[left..right],
+                _             => unreachable!(),
+            }
+        })
+    }
 }
 
-// impl ExactSizeIterator for QueryIndexGroups<'_, '_> {
-//     fn len(&self) -> usize {
-//         self.windows.len() // FIXME (+1) ?
-//     }
-// }
+impl ExactSizeIterator for QueryIndexGroups<'_, '_> {
+    #[inline]
+    fn len(&self) -> usize {
+        self.windows.len()
+    }
+}
+
+impl FusedIterator for QueryIndexGroups<'_, '_> { }
+
+impl DoubleEndedIterator for QueryIndexGroups<'_, '_> {
+    #[inline]
+    fn next_back(&mut self) -> Option<Self::Item> {
+        self.windows.next_back().map(|range| {
+            match *range {
+                [left, right] => &self.matches[left..right],
+                _             => unreachable!(),
+            }
+        })
+    }
+}

From 0d07af3caf432f6a4c62125f5765c191881f637a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= <renault.cle@gmail.com>
Date: Sun, 6 Jan 2019 11:41:47 +0100
Subject: [PATCH 5/7] fix: Filter and count the exact matching words

---
 src/rank/criterion/exact.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/rank/criterion/exact.rs b/src/rank/criterion/exact.rs
index 6d33a6f38..759bd951e 100644
--- a/src/rank/criterion/exact.rs
+++ b/src/rank/criterion/exact.rs
@@ -9,13 +9,13 @@ use crate::database::DatabaseView;
 use crate::Match;
 
 #[inline]
-fn contains_exact(matches: &[Match]) -> bool {
+fn contains_exact(matches: &&[Match]) -> bool {
     matches.iter().any(|m| m.is_exact)
 }
 
 #[inline]
 fn number_exact_matches(matches: &Matches) -> usize {
-    matches.query_index_groups().map(contains_exact).count()
+    matches.query_index_groups().filter(contains_exact).count()
 }
 
 #[derive(Debug, Clone, Copy)]

From d899b8660382ea5a4c211761d28ea31e885a46ce Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= <renault.cle@gmail.com>
Date: Sun, 6 Jan 2019 12:33:27 +0100
Subject: [PATCH 6/7] feat: Prefer using ranges and not using unreachable!

---
 src/rank/mod.rs           | 54 ++++++++++++++++-----------------------
 src/rank/query_builder.rs |  2 +-
 2 files changed, 23 insertions(+), 33 deletions(-)

diff --git a/src/rank/mod.rs b/src/rank/mod.rs
index 2023cb266..91392cbee 100644
--- a/src/rank/mod.rs
+++ b/src/rank/mod.rs
@@ -3,7 +3,8 @@ mod query_builder;
 mod distinct_map;
 
 use std::iter::FusedIterator;
-use std::slice::Windows;
+use std::slice::Iter;
+use std::ops::Range;
 
 use sdset::SetBuf;
 use group_by::GroupBy;
@@ -43,18 +44,19 @@ impl Document {
 #[derive(Debug, Clone)]
 pub struct Matches {
     matches: SetBuf<Match>,
-    slices: Vec<usize>,
+    slices: Vec<Range<usize>>,
 }
 
 impl Matches {
     pub fn new(matches: SetBuf<Match>) -> Matches {
-        let mut last = 0;
-        let mut slices = vec![0];
+        let mut last_end = 0;
+        let mut slices = Vec::new();
 
         for group in GroupBy::new(&matches, match_query_index) {
-            let index = last + group.len();
-            slices.push(index);
-            last = index;
+            let start = last_end;
+            let end = last_end + group.len();
+            slices.push(Range { start, end });
+            last_end = end;
         }
 
         Matches { matches, slices }
@@ -69,7 +71,7 @@ impl Matches {
     pub fn query_index_groups(&self) -> QueryIndexGroups {
         QueryIndexGroups {
             matches: &self.matches,
-            windows: self.slices.windows(2),
+            slices: self.slices.iter(),
         }
     }
 
@@ -80,7 +82,7 @@ impl Matches {
 
 pub struct QueryIndexGroups<'a, 'b> {
     matches: &'a [Match],
-    windows: Windows<'b, usize>,
+    slices: Iter<'b, Range<usize>>,
 }
 
 impl<'a> Iterator for QueryIndexGroups<'a, '_> {
@@ -88,17 +90,14 @@ impl<'a> Iterator for QueryIndexGroups<'a, '_> {
 
     #[inline]
     fn next(&mut self) -> Option<Self::Item> {
-        self.windows.next().map(|range| {
-            match *range {
-                [left, right] => &self.matches[left..right],
-                _             => unreachable!(),
-            }
+        self.slices.next().cloned().map(|range| {
+            unsafe { self.matches.get_unchecked(range) }
         })
     }
 
     #[inline]
     fn size_hint(&self) -> (usize, Option<usize>) {
-        self.windows.size_hint()
+        self.slices.size_hint()
     }
 
     #[inline]
@@ -108,22 +107,16 @@ impl<'a> Iterator for QueryIndexGroups<'a, '_> {
 
     #[inline]
     fn nth(&mut self, n: usize) -> Option<Self::Item> {
-        self.windows.nth(n).map(|range| {
-            match *range {
-                [left, right] => &self.matches[left..right],
-                _             => unreachable!(),
-            }
+        self.slices.nth(n).cloned().map(|range| {
+            unsafe { self.matches.get_unchecked(range) }
         })
     }
 
     #[inline]
     fn last(self) -> Option<Self::Item> {
-        let (matches, windows) = (self.matches, self.windows);
-        windows.last().map(|range| {
-            match *range {
-                [left, right] => &matches[left..right],
-                _             => unreachable!(),
-            }
+        let (matches, slices) = (self.matches, self.slices);
+        slices.last().cloned().map(|range| {
+            unsafe { matches.get_unchecked(range) }
         })
     }
 }
@@ -131,7 +124,7 @@ impl<'a> Iterator for QueryIndexGroups<'a, '_> {
 impl ExactSizeIterator for QueryIndexGroups<'_, '_> {
     #[inline]
     fn len(&self) -> usize {
-        self.windows.len()
+        self.slices.len()
     }
 }
 
@@ -140,11 +133,8 @@ impl FusedIterator for QueryIndexGroups<'_, '_> { }
 impl DoubleEndedIterator for QueryIndexGroups<'_, '_> {
     #[inline]
     fn next_back(&mut self) -> Option<Self::Item> {
-        self.windows.next_back().map(|range| {
-            match *range {
-                [left, right] => &self.matches[left..right],
-                _             => unreachable!(),
-            }
+        self.slices.next_back().cloned().map(|range| {
+            unsafe { self.matches.get_unchecked(range) }
         })
     }
 }
diff --git a/src/rank/query_builder.rs b/src/rank/query_builder.rs
index 9a17e0473..3ec49d83d 100644
--- a/src/rank/query_builder.rs
+++ b/src/rank/query_builder.rs
@@ -116,7 +116,7 @@ where D: Deref<Target=DB>,
             }
         }
 
-        matches.into_iter().map(|(id, m)| Document::from_unsorted_matches(id, m)).collect()
+        matches.into_iter().map(|(i, m)| Document::from_unsorted_matches(i, m)).collect()
     }
 }
 

From c74caa0f82ece8abb9179b3cf1416f6ac5e76bbb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cl=C3=A9ment=20Renault?= <renault.cle@gmail.com>
Date: Sun, 6 Jan 2019 13:18:04 +0100
Subject: [PATCH 7/7] feat: Sum usizes instead of little u16/u32

---
 src/rank/criterion/sum_of_words_attribute.rs | 4 ++--
 src/rank/criterion/sum_of_words_position.rs  | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/rank/criterion/sum_of_words_attribute.rs b/src/rank/criterion/sum_of_words_attribute.rs
index cfcaaec68..aea21c35f 100644
--- a/src/rank/criterion/sum_of_words_attribute.rs
+++ b/src/rank/criterion/sum_of_words_attribute.rs
@@ -8,11 +8,11 @@ use crate::database::DatabaseView;
 use crate::rank::criterion::Criterion;
 
 #[inline]
-fn sum_matches_attributes(matches: &Matches) -> u16 {
+fn sum_matches_attributes(matches: &Matches) -> usize {
     // note that GroupBy will never return an empty group
     // so we can do this assumption safely
     matches.query_index_groups().map(|group| {
-        unsafe { group.get_unchecked(0).attribute.attribute() }
+        unsafe { group.get_unchecked(0).attribute.attribute() as usize }
     }).sum()
 }
 
diff --git a/src/rank/criterion/sum_of_words_position.rs b/src/rank/criterion/sum_of_words_position.rs
index e1d650534..0b27184ba 100644
--- a/src/rank/criterion/sum_of_words_position.rs
+++ b/src/rank/criterion/sum_of_words_position.rs
@@ -8,11 +8,11 @@ use crate::rank::criterion::Criterion;
 use crate::database::DatabaseView;
 
 #[inline]
-fn sum_matches_attribute_index(matches: &Matches) -> u32 {
+fn sum_matches_attribute_index(matches: &Matches) -> usize {
     // note that GroupBy will never return an empty group
     // so we can do this assumption safely
     matches.query_index_groups().map(|group| {
-        unsafe { group.get_unchecked(0).attribute.word_index() }
+        unsafe { group.get_unchecked(0).attribute.word_index() as usize }
     }).sum()
 }