Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Min max size functions added #792

Merged
merged 7 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions lib/src/metta/runner/stdlib.metta
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,24 @@
(@param "Tail of an expression")))
(@return "New expression consists of two input arguments"))

(@doc min-atom
(@desc "Returns atom with min value in the expression (first argument). Only numbers allowed")
(@params (
(@param "Expression")))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(@param "Expression")))
(@param "Expression which contains atoms of Number type")))

(@return "Min value in the expression. Error if expression contains non-numeric value or is empty"))

(@doc max-atom
(@desc "Returns atom with max value in the expression (first argument). Only numbers allowed")
(@params (
(@param "Expression")))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(@param "Expression")))
(@param "Expression which contains atoms of Number type")))

(@return "Max value in the expression. Error if expression contains non-numeric value or is empty"))

(@doc size-atom
(@desc "Returns size of an expression (first argument)")
(@params (
(@param "Expression")))
(@return "Size of an expression"))

(@doc index-atom
(@desc "Returns atom from an expression (first argument) using index (second argument) or error if index is out of bounds")
(@params (
Expand Down
122 changes: 122 additions & 0 deletions lib/src/metta/runner/stdlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1161,6 +1161,94 @@ impl CustomExecute for IntersectionAtomOp {
}
}

#[derive(Clone, Debug)]
pub struct MaxAtomOp {}

grounded_op!(MaxAtomOp, "max-atom");

impl Grounded for MaxAtomOp {
fn type_(&self) -> Atom {
Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_ATOM])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_ATOM])
Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_NUMBER])

}

fn as_execute(&self) -> Option<&dyn CustomExecute> {
Some(self)
}
}

impl CustomExecute for MaxAtomOp {
fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
let arg_error = || ExecError::from("max-atom expects one argument: expression");
let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children();
for x in children.iter() {
match AsPrimitive::from_atom(x).as_number() {
None => Err(ExecError::from("Only numbers allowed in expression")),
_ => Ok({}),
}?
};
match children.into_iter().map(|x| Into::<f64>::into(AsPrimitive::from_atom(x).as_number().unwrap())).reduce(f64::max) {
Some(max) => Ok(vec![Atom::gnd(Number::Float(max))]),
None => Err(ExecError::from("Empty expression")),
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See min-atom comments:

Suggested change
for x in children.iter() {
match AsPrimitive::from_atom(x).as_number() {
None => Err(ExecError::from("Only numbers allowed in expression")),
_ => Ok({}),
}?
};
match children.into_iter().map(|x| Into::<f64>::into(AsPrimitive::from_atom(x).as_number().unwrap())).reduce(f64::max) {
Some(max) => Ok(vec![Atom::gnd(Number::Float(max))]),
None => Err(ExecError::from("Empty expression")),
}
if children.is_empty() {
Err(ExecError::from("Empty expression"))
} else {
children.into_iter().fold(Ok(f64::NEG_INFINITY), |res, x| {
match (res, AsPrimitive::from_atom(x).as_number()) {
(res @ Err(_), _) => res,
(_, None) => Err(ExecError::from("Only numbers are allowed in expression")),
(Ok(max), Some(x)) => Ok(f64::max(max, x.into())),
}
})
}.map(|max| vec![Atom::gnd(Number::Float(max))])

}
}

#[derive(Clone, Debug)]
pub struct MinAtomOp {}

grounded_op!(MinAtomOp, "min-atom");

impl Grounded for MinAtomOp {
fn type_(&self) -> Atom {
Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_ATOM])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_ATOM])
Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_NUMBER])

}

fn as_execute(&self) -> Option<&dyn CustomExecute> {
Some(self)
}
}

impl CustomExecute for MinAtomOp {
fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
let arg_error = || ExecError::from("min-atom expects one argument: expression");
let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children();
for x in children.iter() {
match AsPrimitive::from_atom(x).as_number() {
None => Err(ExecError::from("Only numbers allowed in expression")),
_ => Ok({}),
}?
};
match children.into_iter().map(|x| Into::<f64>::into(AsPrimitive::from_atom(x).as_number().unwrap())).reduce(f64::min) {
Some(min) => Ok(vec![Atom::gnd(Number::Float(min))]),
None => Err(ExecError::from("Empty expression")),
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. One doesn't need to iterate through children twice, fold allows doing this in one step
  2. Into::<> is needed only when compiler cannot inference the proper type from context, here it is not needed
    I would suggest replacing code by the following:
Suggested change
for x in children.iter() {
match AsPrimitive::from_atom(x).as_number() {
None => Err(ExecError::from("Only numbers allowed in expression")),
_ => Ok({}),
}?
};
match children.into_iter().map(|x| Into::<f64>::into(AsPrimitive::from_atom(x).as_number().unwrap())).reduce(f64::min) {
Some(min) => Ok(vec![Atom::gnd(Number::Float(min))]),
None => Err(ExecError::from("Empty expression")),
}
if children.is_empty() {
Err(ExecError::from("Empty expression"))
} else {
children.into_iter().fold(Ok(f64::INFINITY), |res, x| {
match (res, AsPrimitive::from_atom(x).as_number()) {
(res @ Err(_), _) => res,
(_, None) => Err(ExecError::from("Only numbers are allowed in expression")),
(Ok(min), Some(x)) => Ok(f64::min(min, x.into())),
}
})
}.map(|min| vec![Atom::gnd(Number::Float(min))])

}
}

#[derive(Clone, Debug)]
pub struct SizeAtomOp {}

grounded_op!(SizeAtomOp, "size-atom");

impl Grounded for SizeAtomOp {
fn type_(&self) -> Atom {
Atom::expr([ARROW_SYMBOL, ATOM_TYPE_EXPRESSION, ATOM_TYPE_NUMBER])
}

fn as_execute(&self) -> Option<&dyn CustomExecute> {
Some(self)
}
}

impl CustomExecute for SizeAtomOp {
fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
let arg_error = || ExecError::from("size-atom expects one argument: expression");
let children = TryInto::<&ExpressionAtom>::try_into(args.get(0).ok_or_else(arg_error)?)?.children();
let size = children.len();
Ok(vec![Atom::gnd(Number::Integer(size as i64))])
}
}

#[derive(Clone, Debug)]
pub struct IndexAtomOp {}

Expand Down Expand Up @@ -1829,6 +1917,12 @@ mod non_minimal_only_stdlib {
tref.register_token(regex(r"cdr-atom"), move |_| { cdr_atom_op.clone() });
let cons_atom_op = Atom::gnd(ConsAtomOp{});
tref.register_token(regex(r"cons-atom"), move |_| { cons_atom_op.clone() });
let max_atom_op = Atom::gnd(MaxAtomOp{});
tref.register_token(regex(r"max-atom"), move |_| { max_atom_op.clone() });
let min_atom_op = Atom::gnd(MinAtomOp{});
tref.register_token(regex(r"min-atom"), move |_| { min_atom_op.clone() });
let size_atom_op = Atom::gnd(SizeAtomOp{});
tref.register_token(regex(r"size-atom"), move |_| { size_atom_op.clone() });
let index_atom_op = Atom::gnd(IndexAtomOp{});
tref.register_token(regex(r"index-atom"), move |_| { index_atom_op.clone() });
let random_int_op = Atom::gnd(RandomIntOp{});
Expand Down Expand Up @@ -2122,6 +2216,34 @@ mod tests {
assert_eq!(res, vec![expr!(("A" "F") ("B" "C") "D")]);
}

#[test]
fn size_atom_op() {
let res = SizeAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Integer(3)} {Number::Integer(2)} {Number::Integer(1)})]).expect("No result returned");
assert_eq!(res, vec![expr!({Number::Integer(5)})]);
let res = SizeAtomOp{}.execute(&mut vec![expr!()]).expect("No result returned");
assert_eq!(res, vec![expr!({Number::Integer(0)})]);
}

#[test]
fn min_atom_op() {
let res = MinAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Float(5.5)})]).expect("No result returned");
assert_eq!(res, vec![expr!({Number::Integer(4)})]);
let res = MinAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} "A")]);
assert_eq!(res, Err(ExecError::from("Only numbers allowed in expression")));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert_eq!(res, Err(ExecError::from("Only numbers allowed in expression")));
assert_eq!(res, Err(ExecError::from("Only numbers are allowed in expression")));

let res = MinAtomOp{}.execute(&mut vec![expr!()]);
assert_eq!(res, Err(ExecError::from("Empty expression")));
}

#[test]
fn max_atom_op() {
let res = MaxAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Float(5.5)})]).expect("No result returned");
assert_eq!(res, vec![expr!({Number::Float(5.5)})]);
let res = MaxAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} "A")]);
assert_eq!(res, Err(ExecError::from("Only numbers allowed in expression")));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert_eq!(res, Err(ExecError::from("Only numbers allowed in expression")));
assert_eq!(res, Err(ExecError::from("Only numbers are allowed in expression")));

let res = MaxAtomOp{}.execute(&mut vec![expr!()]);
assert_eq!(res, Err(ExecError::from("Empty expression")));
}

#[test]
fn index_atom_op() {
let res = IndexAtomOp{}.execute(&mut vec![expr!({Number::Integer(5)} {Number::Integer(4)} {Number::Integer(3)} {Number::Integer(2)} {Number::Integer(1)}), expr!({Number::Integer(2)})]).expect("No result returned");
Expand Down
18 changes: 18 additions & 0 deletions lib/src/metta/runner/stdlib_minimal.metta
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,24 @@
(@return "Deconsed expression"))
(: decons-atom (-> Expression Expression))

(@doc min-atom
(@desc "Returns atom with min value in the expression (first argument). Only numbers allowed")
(@params (
(@param "Expression")))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(@param "Expression")))
(@param "Expression which contains atoms of Number type")))

(@return "Min value in the expression. Error if expression contains non-numeric value or is empty"))

(@doc max-atom
(@desc "Returns atom with max value in the expression (first argument). Only numbers allowed")
(@params (
(@param "Expression")))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(@param "Expression")))
(@param "Expression which contains atoms of Number type")))

(@return "Max value in the expression. Error if expression contains non-numeric value or is empty"))

(@doc size-atom
(@desc "Returns size of an expression (first argument)")
(@params (
(@param "Expression")))
(@return "Size of an expression"))

(@doc index-atom
(@desc "Returns atom from an expression (first argument) using index (second argument) or error if index is out of bounds")
(@params (
Expand Down
26 changes: 26 additions & 0 deletions lib/src/metta/runner/stdlib_minimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,12 @@ pub fn register_common_tokens(tref: &mut Tokenizer, _tokenizer: Shared<Tokenizer
tref.register_token(regex(r"nop"), move |_| { nop_op.clone() });
let match_op = Atom::gnd(stdlib::MatchOp{});
tref.register_token(regex(r"match"), move |_| { match_op.clone() });
let min_atom_op = Atom::gnd(stdlib::MinAtomOp{});
tref.register_token(regex(r"min-atom"), move |_| { min_atom_op.clone() });
let max_atom_op = Atom::gnd(stdlib::MaxAtomOp{});
tref.register_token(regex(r"max-atom"), move |_| { max_atom_op.clone() });
let size_atom_op = Atom::gnd(stdlib::SizeAtomOp{});
tref.register_token(regex(r"size-atom"), move |_| { size_atom_op.clone() });
let index_atom_op = Atom::gnd(stdlib::IndexAtomOp{});
tref.register_token(regex(r"index-atom"), move |_| { index_atom_op.clone() });
let random_int_op = Atom::gnd(stdlib::RandomIntOp{});
Expand Down Expand Up @@ -621,6 +627,26 @@ mod tests {
assert_eq!(run_program(&format!("!(cdr-atom $a)")), Ok(vec![vec![expr!("Error" ("cdr-atom" a) {Str::from_str("cdr-atom expects a non-empty expression as an argument")})]]));
}

#[test]
fn metta_size_atom() {
assert_eq!(run_program(&format!("!(size-atom (5 4 3 2 1))")), Ok(vec![vec![expr!({Number::Integer(5)})]]));
assert_eq!(run_program(&format!("!(size-atom ())")), Ok(vec![vec![expr!({Number::Integer(0)})]]));
}

#[test]
fn metta_min_atom() {
assert_eq!(run_program(&format!("!(min-atom (5 4 5.5))")), Ok(vec![vec![expr!({Number::Integer(4)})]]));
assert_eq!(run_program(&format!("!(min-atom ())")), Ok(vec![vec![expr!("Error" ({ stdlib::MinAtomOp{} } ()) "Empty expression")]]));
assert_eq!(run_program(&format!("!(min-atom (3 A B 5))")), Ok(vec![vec![expr!("Error" ({ stdlib::MinAtomOp{} } ({Number::Integer(3)} "A" "B" {Number::Integer(5)})) "Only numbers allowed in expression")]]));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert_eq!(run_program(&format!("!(min-atom (3 A B 5))")), Ok(vec![vec![expr!("Error" ({ stdlib::MinAtomOp{} } ({Number::Integer(3)} "A" "B" {Number::Integer(5)})) "Only numbers allowed in expression")]]));
assert_eq!(run_program(&format!("!(min-atom (3 A B 5))")), Ok(vec![vec![expr!("Error" ({ stdlib::MinAtomOp{} } ({Number::Integer(3)} "A" "B" {Number::Integer(5)})) "Only numbers are allowed in expression")]]));

}

#[test]
fn metta_max_atom() {
assert_eq!(run_program(&format!("!(max-atom (5 4 5.5))")), Ok(vec![vec![expr!({Number::Float(5.5)})]]));
assert_eq!(run_program(&format!("!(max-atom ())")), Ok(vec![vec![expr!("Error" ({ stdlib::MaxAtomOp{} } ()) "Empty expression")]]));
assert_eq!(run_program(&format!("!(max-atom (3 A B 5))")), Ok(vec![vec![expr!("Error" ({ stdlib::MaxAtomOp{} } ({Number::Integer(3)} "A" "B" {Number::Integer(5)})) "Only numbers allowed in expression")]]));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert_eq!(run_program(&format!("!(max-atom (3 A B 5))")), Ok(vec![vec![expr!("Error" ({ stdlib::MaxAtomOp{} } ({Number::Integer(3)} "A" "B" {Number::Integer(5)})) "Only numbers allowed in expression")]]));
assert_eq!(run_program(&format!("!(max-atom (3 A B 5))")), Ok(vec![vec![expr!("Error" ({ stdlib::MaxAtomOp{} } ({Number::Integer(3)} "A" "B" {Number::Integer(5)})) "Only numbers are allowed in expression")]]));

}

#[test]
fn metta_index_atom() {
assert_eq!(run_program(&format!("!(index-atom (5 4 3 2 1) 2)")), Ok(vec![vec![expr!({Number::Integer(3)})]]));
Expand Down
Loading