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,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(())

@ -45,14 +45,11 @@ pub fn fallback_filter(query: &str) -> Result<RawCardFilter, String> {
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<CardFilter, String> {
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)),

@ -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<dyn std::error::Error>> {
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(())
}

@ -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<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() {
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())));
}
}