From fee7dcc62a7a60d53d4c2b5453b0f2e4579d5af0 Mon Sep 17 00:00:00 2001 From: kageru Date: Fri, 27 Jan 2023 00:03:00 +0100 Subject: [PATCH] sort filters to improve performance --- src/data.rs | 11 +++++++---- src/filter.rs | 3 --- src/main.rs | 13 ++++++++++--- src/parser.rs | 48 +++++++++++++++++++++++++++++++++++------------- 4 files changed, 52 insertions(+), 23 deletions(-) diff --git a/src/data.rs b/src/data.rs index 2d29b38..efd4348 100644 --- a/src/data.rs +++ b/src/data.rs @@ -40,13 +40,16 @@ impl Display for Card { write!(f, "{attr}/")?; } 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")) { + f.write_str("\n")?; 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")?, - (None, Some(def)) => write!(f, " ? ATK / {def} DEF")?, - (Some(atk), None) => write!(f, " {atk} ATK / ? DEF")?, - (None, None) => write!(f, " ? ATK / ? DEF")?, + (None, Some(def)) => write!(f, "? ATK / {def} DEF")?, + (Some(atk), None) => write!(f, "{atk} ATK / ? DEF")?, + (None, None) => write!(f, "? ATK / ? DEF")?, } } Ok(()) diff --git a/src/filter.rs b/src/filter.rs index a8a74bf..37a0743 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -45,14 +45,11 @@ pub fn fallback_filter(query: &str) -> Result { if query.contains(OPERATOR_CHARS) { return Err(format!("Invalid query: {query}")); } - #[cfg(debug_assertions)] - println!("Trying to match {query} as card name"); let q = query.to_lowercase(); Ok((Field::Name, Operator::Equals, Value::String(q))) } pub fn build_filter(query: RawCardFilter) -> Result { - dbg!(&query); Ok(match query { (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)), diff --git a/src/main.rs b/src/main.rs index 6ba5519..370dc00 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ #![feature(option_result_contains)] -use std::collections::HashMap; +use std::{collections::HashMap, time::Instant}; use data::CardInfo; use filter::SearchCard; @@ -12,10 +12,17 @@ fn main() -> Result<(), Box> { 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 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 query = parser::parse_filters(&raw_query)?; - for c in search_cards.iter().filter(|card| query.iter().all(|q| q(card))) { - println!("{}", cards_by_id.get(&c.id).unwrap()); + let query_time = now.elapsed(); + 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(()) } diff --git a/src/parser.rs b/src/parser.rs index f30e632..512e129 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,16 +4,17 @@ use crate::filter::{build_filter, fallback_filter, CardFilter, RawCardFilter}; use nom::{ branch::alt, bytes::complete::{take_until1, take_while, take_while_m_n}, - character::complete::multispace0, + character::complete::{char, multispace0}, combinator::{complete, map_res, rest, verify}, multi::many_m_n, - sequence::{preceded, tuple}, + sequence::{delimited, preceded, tuple}, IResult, }; pub fn parse_filters(input: &str) -> Result, 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() { + v.sort_unstable_by_key(|(f, _, _)| *f as u8); v.into_iter().map(build_filter).collect() } else { 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> { - 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)), Err(_) if i.is_empty() => Err("empty filter argument"), Err(_) => Ok(Value::String(i.to_lowercase())), })(input) } +/// Ordinals are given highest = fastest to filter. +/// This is used to sort filters before applying them. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Field { - Atk, - Def, - Level, - Type, - Attribute, - Class, - Name, - Text, + Text = 0, + Name = 1, + Class = 2, + Attribute = 3, + Type = 4, + Level = 5, + Atk = 6, + Def = 7, } 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_filters("atk>=100 l:4"), + parse_raw_filters("atk>=100 l=4"), Ok(( "", 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()))); } }