sort filters to improve performance

This commit is contained in:
kageru 2023-01-27 00:03:00 +01:00
parent 4995967cf4
commit fee7dcc62a
Signed by: kageru
GPG Key ID: 8282A2BEA4ADA3D2
4 changed files with 52 additions and 23 deletions

@ -40,7 +40,10 @@ impl Display for Card {
write!(f, "{attr}/")?; write!(f, "{attr}/")?;
} }
write!(f, "{} {})", self.r#type, self.card_type)?; write!(f, "{} {})", self.r#type, self.card_type)?;
f.write_str("\n")?;
f.write_str(&self.text)?;
if self.card_type.contains(&String::from("Monster")) { if self.card_type.contains(&String::from("Monster")) {
f.write_str("\n")?;
match (self.atk, self.def) { match (self.atk, self.def) {
(Some(atk), Some(def)) => write!(f, "{atk} ATK / {def} DEF")?, (Some(atk), Some(def)) => write!(f, "{atk} ATK / {def} DEF")?,
(Some(atk), None) if self.link_rating.is_some() => write!(f, "{atk} ATK")?, (Some(atk), None) if self.link_rating.is_some() => write!(f, "{atk} ATK")?,

@ -45,14 +45,11 @@ pub fn fallback_filter(query: &str) -> Result<RawCardFilter, String> {
if query.contains(OPERATOR_CHARS) { if query.contains(OPERATOR_CHARS) {
return Err(format!("Invalid query: {query}")); return Err(format!("Invalid query: {query}"));
} }
#[cfg(debug_assertions)]
println!("Trying to match {query} as card name");
let q = query.to_lowercase(); let q = query.to_lowercase();
Ok((Field::Name, Operator::Equals, Value::String(q))) Ok((Field::Name, Operator::Equals, Value::String(q)))
} }
pub fn build_filter(query: RawCardFilter) -> Result<CardFilter, String> { pub fn build_filter(query: RawCardFilter) -> Result<CardFilter, String> {
dbg!(&query);
Ok(match query { Ok(match query {
(Field::Atk, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.atk, n)), (Field::Atk, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.atk, n)),
(Field::Def, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.def, n)), (Field::Def, op, Value::Numerical(n)) => Box::new(move |card| op.filter_number(card.def, n)),

@ -1,5 +1,5 @@
#![feature(option_result_contains)] #![feature(option_result_contains)]
use std::collections::HashMap; use std::{collections::HashMap, time::Instant};
use data::CardInfo; use data::CardInfo;
use filter::SearchCard; use filter::SearchCard;
@ -12,10 +12,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let cards = serde_json::from_reader::<_, CardInfo>(std::io::BufReader::new(std::fs::File::open("cards.json")?))?.data; let cards = serde_json::from_reader::<_, CardInfo>(std::io::BufReader::new(std::fs::File::open("cards.json")?))?.data;
let search_cards: Vec<_> = cards.iter().map(SearchCard::from).collect(); let search_cards: Vec<_> = cards.iter().map(SearchCard::from).collect();
let cards_by_id: HashMap<_, _> = cards.into_iter().map(|c| (c.id, c)).collect(); let cards_by_id: HashMap<_, _> = cards.into_iter().map(|c| (c.id, c)).collect();
let now = Instant::now();
let raw_query = std::env::args().nth(1).unwrap(); let raw_query = std::env::args().nth(1).unwrap();
let query = parser::parse_filters(&raw_query)?; let query = parser::parse_filters(&raw_query)?;
for c in search_cards.iter().filter(|card| query.iter().all(|q| q(card))) { let query_time = now.elapsed();
println!("{}", cards_by_id.get(&c.id).unwrap()); let now = Instant::now();
let matches: Vec<_> = search_cards.iter().filter(|card| query.iter().all(|q| q(card))).collect();
let filter_time = now.elapsed();
for c in &matches {
println!("{}\n", cards_by_id.get(&c.id).unwrap());
} }
println!("Parsed query in {:?}", query_time);
println!("Searched {} cards in {:?} ({} hits)", search_cards.len(), filter_time, matches.len());
Ok(()) Ok(())
} }

@ -4,16 +4,17 @@ use crate::filter::{build_filter, fallback_filter, CardFilter, RawCardFilter};
use nom::{ use nom::{
branch::alt, branch::alt,
bytes::complete::{take_until1, take_while, take_while_m_n}, bytes::complete::{take_until1, take_while, take_while_m_n},
character::complete::multispace0, character::complete::{char, multispace0},
combinator::{complete, map_res, rest, verify}, combinator::{complete, map_res, rest, verify},
multi::many_m_n, multi::many_m_n,
sequence::{preceded, tuple}, sequence::{delimited, preceded, tuple},
IResult, IResult,
}; };
pub fn parse_filters(input: &str) -> Result<Vec<CardFilter>, String> { pub fn parse_filters(input: &str) -> Result<Vec<CardFilter>, String> {
parse_raw_filters(input).map_err(|e| format!("Error while parsing filters “{input}”: {e:?}")).and_then(|(rest, v)| { parse_raw_filters(input).map_err(|e| format!("Error while parsing filters “{input}”: {e:?}")).and_then(|(rest, mut v)| {
if rest.is_empty() { if rest.is_empty() {
v.sort_unstable_by_key(|(f, _, _)| *f as u8);
v.into_iter().map(build_filter).collect() v.into_iter().map(build_filter).collect()
} else { } else {
Err(format!("Input was not fully parsed. Left over: “{rest}")) Err(format!("Input was not fully parsed. Left over: “{rest}"))
@ -44,23 +45,25 @@ fn operator(input: &str) -> IResult<&str, Operator> {
} }
fn value(input: &str) -> IResult<&str, Value> { fn value(input: &str) -> IResult<&str, Value> {
map_res(alt((take_until1(" "), rest)), |i: &str| match i.parse() { map_res(alt((delimited(char('"'), take_until1("\""), char('"')), take_until1(" "), rest)), |i: &str| match i.parse() {
Ok(n) => Ok(Value::Numerical(n)), Ok(n) => Ok(Value::Numerical(n)),
Err(_) if i.is_empty() => Err("empty filter argument"), Err(_) if i.is_empty() => Err("empty filter argument"),
Err(_) => Ok(Value::String(i.to_lowercase())), Err(_) => Ok(Value::String(i.to_lowercase())),
})(input) })(input)
} }
/// Ordinals are given highest = fastest to filter.
/// This is used to sort filters before applying them.
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Field { pub enum Field {
Atk, Text = 0,
Def, Name = 1,
Level, Class = 2,
Type, Attribute = 3,
Attribute, Type = 4,
Class, Level = 5,
Name, Atk = 6,
Text, Def = 7,
} }
impl FromStr for Field { impl FromStr for Field {
@ -156,11 +159,30 @@ mod tests {
assert_eq!(parse_raw_filter(rest), Ok(("", (Field::Level, Operator::Equals, Value::Numerical(4))))); assert_eq!(parse_raw_filter(rest), Ok(("", (Field::Level, Operator::Equals, Value::Numerical(4)))));
assert_eq!( assert_eq!(
parse_raw_filters("atk>=100 l:4"), parse_raw_filters("atk>=100 l=4"),
Ok(( Ok((
"", "",
vec![(Field::Atk, Operator::GreaterEqual, Value::Numerical(100)), (Field::Level, Operator::Equals, Value::Numerical(4))] vec![(Field::Atk, Operator::GreaterEqual, Value::Numerical(100)), (Field::Level, Operator::Equals, Value::Numerical(4))]
)) ))
); );
assert_eq!(
parse_raw_filters(r#"t:counter c:trap o:"negate the summon""#),
Ok((
"",
vec![
(Field::Type, Operator::Equals, Value::String("counter".into())),
(Field::Class, Operator::Equals, Value::String("trap".into())),
(Field::Text, Operator::Equals, Value::String("negate the summon".into())),
]
))
);
}
#[test]
fn quoted_value_test() {
let (rest, filter) = parse_raw_filter(r#"o:"destroy that target""#).unwrap();
assert_eq!(rest, "");
assert_eq!(filter, (Field::Text, Operator::Equals, Value::String("destroy that target".into())));
} }
} }