2019-04-20 01:24:19 +02:00
use crate ::model ::* ;
2019-05-07 18:36:23 +02:00
mod query_filter ;
pub use self ::query_filter ::QueryFilterSyntaxError ;
use self ::query_filter ::* ;
2019-04-20 01:24:19 +02:00
pub struct Query {
2019-05-07 17:36:32 +02:00
filters : Vec < QueryFilter > ,
2019-04-20 01:24:19 +02:00
}
impl Query {
2019-05-07 18:36:23 +02:00
pub fn search ( & self , input : & mut Iterator < Item = & Texture > ) -> Vec < Texture > {
2019-05-07 21:18:05 +02:00
let mut results : Vec < ( i64 , & Texture ) > = Vec ::new ( ) ;
2019-05-07 18:36:23 +02:00
// 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.
2019-05-07 21:18:05 +02:00
let required_score = self . required_score ( ) * 1_000_000 i64 ;
2019-05-07 18:36:23 +02:00
2019-05-07 21:18:05 +02:00
' texture_loop : for texture in input {
let mut score = 0 i64 ;
2019-05-07 18:36:23 +02:00
for ( pos , filter ) in self . filters . iter ( ) . enumerate ( ) {
match filter . score ( texture ) {
Score ::RequiredMatch ( true ) = > ( ) ,
Score ::RequiredMatch ( false ) = > {
// skip this texture
2019-05-07 21:18:05 +02:00
continue 'texture_loop ;
2019-05-07 18:36:23 +02:00
}
Score ::Match ( true ) = > {
2019-05-07 21:18:05 +02:00
score + = 1_000_000 i64 + ( ( 100_000.0 / f64 ::sqrt ( pos as f64 + 1.0 ) ) as i64 ) ;
2019-05-07 18:36:23 +02:00
}
Score ::Match ( false ) = > ( ) ,
}
}
2019-05-07 17:36:32 +02:00
2019-05-07 18:36:23 +02:00
if score > = required_score {
results . push ( ( score , texture ) )
}
2019-05-07 17:36:32 +02:00
}
2019-05-07 21:18:05 +02:00
results . sort_by_key ( | ( score , _ ) | score * - 1 ) ;
2019-05-07 17:36:32 +02:00
2019-05-07 18:36:23 +02:00
results
. iter ( )
. map ( | ( _ , texture ) | Texture ::clone ( texture ) )
. collect ::< Vec < Texture > > ( )
2019-04-20 01:24:19 +02:00
}
2019-05-07 17:36:32 +02:00
2019-05-07 21:18:05 +02:00
fn required_score ( & self ) -> i64 {
let non_special = self . filters . iter ( ) . filter ( | f | ! f . is_special ( ) ) . count ( ) as i64 ;
2019-05-07 17:36:32 +02:00
2019-05-07 18:36:23 +02:00
// ceil(non_special / 2)
( non_special + 1 ) / 2
2019-05-07 17:36:32 +02:00
}
2019-05-07 18:36:23 +02:00
pub fn parse ( input : & [ String ] ) -> Result < Query , QueryFilterSyntaxError > {
let mut result = Query { filters : vec ! [ ] } ;
2019-05-07 17:36:32 +02:00
2019-05-07 18:36:23 +02:00
for fltr in input {
let qryfltr = fltr . parse ::< QueryFilter > ( ) ? ;
result . filters . push ( qryfltr ) ;
2019-05-07 17:36:32 +02:00
}
2019-05-07 18:36:23 +02:00
Ok ( result )
2019-05-07 17:36:32 +02:00
}
2019-04-24 17:01:43 +02:00
}
2019-05-07 21:18:05 +02:00
#[ 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 ] ) ;
}
}