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, "{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")?,
|
||||||
(None, Some(def)) => write!(f, " ? ATK / {def} DEF")?,
|
(None, Some(def)) => write!(f, "? ATK / {def} DEF")?,
|
||||||
(Some(atk), None) => write!(f, " {atk} ATK / ? DEF")?,
|
(Some(atk), None) => write!(f, "{atk} ATK / ? DEF")?,
|
||||||
(None, None) => write!(f, " ? ATK / ? DEF")?,
|
(None, None) => write!(f, "? ATK / ? DEF")?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -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)),
|
||||||
|
13
src/main.rs
13
src/main.rs
@ -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())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user