sort filters to improve performance
This commit is contained in:
parent
4995967cf4
commit
fee7dcc62a
11
src/data.rs
11
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(())
|
||||
|
@ -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)),
|
||||
|
13
src/main.rs
13
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<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())));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user