166 lines
4.8 KiB
Rust
166 lines
4.8 KiB
Rust
use crate::model::*;
|
|
|
|
mod query_filter;
|
|
pub use self::query_filter::QueryFilterSyntaxError;
|
|
use self::query_filter::*;
|
|
|
|
pub struct Query {
|
|
filters: Vec<QueryFilter>,
|
|
}
|
|
|
|
impl Query {
|
|
pub fn search(&self, input: &mut Iterator<Item = &Texture>) -> Vec<Texture> {
|
|
let mut results: Vec<(i64, &Texture)> = Vec::new();
|
|
|
|
// We use pseudo decimal fixed point numbers here.
|
|
// 1.000_000 = 1_000_000;
|
|
|
|
// This is done, since algorithms like quicksort can fail on floats.
|
|
let required_score = self.required_score() * 1_000_000i64;
|
|
|
|
'texture_loop: for texture in input {
|
|
let mut score = 0i64;
|
|
|
|
for (pos, filter) in self.filters.iter().enumerate() {
|
|
match filter.score(texture) {
|
|
Score::RequiredMatch(true) => (),
|
|
Score::RequiredMatch(false) => {
|
|
// skip this texture
|
|
continue 'texture_loop;
|
|
}
|
|
Score::Match(true) => {
|
|
score += 1_000_000i64 + ((100_000.0 / f64::sqrt(pos as f64 + 1.0)) as i64);
|
|
}
|
|
Score::Match(false) => (),
|
|
}
|
|
}
|
|
|
|
if score >= required_score {
|
|
results.push((score, texture))
|
|
}
|
|
}
|
|
|
|
results.sort_by_key(|(score, _)| score * -1);
|
|
|
|
results
|
|
.iter()
|
|
.map(|(_, texture)| Texture::clone(texture))
|
|
.collect::<Vec<Texture>>()
|
|
}
|
|
|
|
fn required_score(&self) -> i64 {
|
|
let non_special = self.filters.iter().filter(|f| !f.is_special()).count() as i64;
|
|
|
|
// ceil(non_special / 2)
|
|
(non_special + 1) / 2
|
|
}
|
|
|
|
pub fn parse(input: &[String]) -> Result<Query, QueryFilterSyntaxError> {
|
|
let mut result = Query { filters: vec![] };
|
|
|
|
for fltr in input {
|
|
let qryfltr = fltr.parse::<QueryFilter>()?;
|
|
result.filters.push(qryfltr);
|
|
}
|
|
|
|
Ok(result)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
/// These tests check against the example section in the search design document.
|
|
mod test {
|
|
use super::*;
|
|
use std::str::FromStr;
|
|
|
|
/// just a shorthand
|
|
fn tex(id: i32, name: &str, tags: &str, added_on: &str, resolution: u64) -> Texture {
|
|
Texture {
|
|
id: format!("{}", id), // Id should actaly be a uuid, but for testing this is fine.
|
|
name: name.to_string(),
|
|
tags: tags.split(",").map(|s| s.trim().to_string()).collect(),
|
|
added_on: Date::from_str(added_on).unwrap(),
|
|
resolution: (resolution, resolution),
|
|
format: TextureFormat::JPEG,
|
|
texture_hash: Sha256::from_data(b"Some Hash"),
|
|
}
|
|
}
|
|
|
|
// data of example section of search document.
|
|
fn test_data() -> Vec<Texture> {
|
|
vec![
|
|
tex(
|
|
1,
|
|
"wood_185841",
|
|
"Holz, Dunkel, Rot, Edel",
|
|
"2019-05-15",
|
|
4 * 1024,
|
|
),
|
|
tex(2, "wood_84846", "Holz, Hell", "2019-05-13", 2 * 1024),
|
|
tex(3, "silk_large", "Stoff, Rot, Edel", "2018-01-01", 8 * 1024),
|
|
tex(4, "cotton_xxx", "Stoff, Rot, Rau", "2018-02-01", 2048),
|
|
tex(5, "green_frabric", "Grün, Stoff", "2018-03-01", 1024),
|
|
tex(6, "tin54_45", "Metall, Hell", "2018-03-01", 4 * 1024),
|
|
tex(7, "copper4_1k", "Rot, Metall", "2016-03-01", 1024),
|
|
tex(
|
|
8,
|
|
"rusty_metall",
|
|
"Rot, Metall, Rost, Dunkel",
|
|
"2015-03-01",
|
|
8 * 1024,
|
|
),
|
|
]
|
|
}
|
|
|
|
fn assert_query(query: &str, expected: Vec<u8>) {
|
|
let q = Query::parse(
|
|
&query
|
|
.split_whitespace()
|
|
.map(|s| s.to_string())
|
|
.collect::<Vec<String>>(),
|
|
)
|
|
.unwrap();
|
|
|
|
let data = test_data();
|
|
|
|
let result = q.search(&mut data.iter());
|
|
|
|
let only_ids = result
|
|
.into_iter()
|
|
.map(|tex| tex.id.parse::<u8>().unwrap())
|
|
.collect::<Vec<u8>>();
|
|
|
|
assert_eq!(only_ids, expected)
|
|
}
|
|
|
|
#[test]
|
|
fn test_examples_from_doc_1() {
|
|
assert_query("Holz Dunkel", vec![1, 2, 8]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_examples_from_doc_2() {
|
|
assert_query("n:wood_", vec![1, 2]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_examples_from_doc_3() {
|
|
assert_query("before:2019-05-31 after:2019-04-30", vec![1, 2]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_examples_from_doc_4() {
|
|
assert_query("Stoff Rot Edel", vec![3, 4, 1]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_examples_from_doc_5() {
|
|
assert_query("Stoff Rot !Edel", vec![4, 3, 5, 7, 8]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_examples_from_doc_6() {
|
|
assert_query("Metall Dunkel res:4k", vec![8, 6, 1]);
|
|
}
|
|
}
|