use ocas_atom::{Atom, AtomArena, AtomNode};
#[cfg(test)]
use ocas_core::arena::Arena;
use crate::combinatorics;
pub fn partition_expr<'a>(
ctx: &'a AtomArena<'a>,
expr: Atom<'a>,
bins: &[(ocas_atom::Symbol, usize)],
fill_last: bool,
repeat: bool,
) -> Atom<'a> {
let args: &[Atom<'a>] = match expr.node() {
AtomNode::Fun(name, a) if name.as_str() == "arg" => a,
_ => return expr,
};
if args.is_empty() || bins.is_empty() {
return ctx.num(0);
}
let elements: Vec<i64> = args
.iter()
.filter_map(|a| match a.node() {
AtomNode::Num(n) => Some(*n),
_ => None,
})
.collect();
if elements.len() != args.len() {
return expr;
}
let bin_specs: Vec<(ocas_atom::Symbol, usize)> = bins.to_vec();
let sols = combinatorics::partitions(&elements, &bin_specs, fill_last, repeat);
if sols.is_empty() {
return ctx.num(0);
}
let mut terms: Vec<Atom<'a>> = Vec::new();
for sol in &sols {
let coeff_atom = ctx.num(sol.coefficient as i64);
let mut factors: Vec<Atom<'a>> = vec![coeff_atom];
for (name, content) in &sol.bins {
let content_atoms: Vec<Atom<'a>> = content.iter().map(|&n| ctx.num(n)).collect();
factors.push(ctx.fun(name.as_str(), &content_atoms));
}
terms.push(ctx.mul(&factors));
}
if terms.is_empty() {
ctx.num(0)
} else if terms.len() == 1 {
terms.pop().unwrap()
} else {
ctx.add(&terms)
}
}
fn recurse<'a, F>(ctx: &'a AtomArena<'a>, atom: Atom<'a>, f: &mut F) -> Atom<'a>
where
F: FnMut(Atom<'a>) -> Option<Atom<'a>>,
{
let rebuilt = match atom.node() {
AtomNode::Num(_) | AtomNode::Var(_) => atom,
AtomNode::Add(args) => {
let new_args: Vec<Atom<'a>> = args.iter().map(|a| recurse(ctx, *a, f)).collect();
ctx.add(&new_args)
}
AtomNode::Mul(args) => {
let new_args: Vec<Atom<'a>> = args.iter().map(|a| recurse(ctx, *a, f)).collect();
ctx.mul(&new_args)
}
AtomNode::Pow(base, exp) => {
let new_base = recurse(ctx, *base, f);
let new_exp = recurse(ctx, *exp, f);
ctx.pow(new_base, new_exp)
}
AtomNode::Fun(name, args) => {
let new_args: Vec<Atom<'a>> = args.iter().map(|a| recurse(ctx, *a, f)).collect();
ctx.fun(name.as_str(), &new_args)
}
};
f(rebuilt).unwrap_or(rebuilt)
}
pub fn transform<'a, F>(ctx: &'a AtomArena<'a>, atom: Atom<'a>, mut f: F) -> Atom<'a>
where
F: FnMut(Atom<'a>) -> Option<Atom<'a>>,
{
recurse(ctx, atom, &mut f)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn transform_add_children() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let x = ctx.var("x");
let y = ctx.var("y");
let z = ctx.var("z");
let sum = ctx.add(&[x, y, z]);
let result = transform(&ctx, sum, |a| match a.node() {
AtomNode::Var(s) if s.as_str() == "x" => Some(ctx.var("a")),
_ => None,
});
assert_eq!(result.to_string(), "a + y + z");
}
#[test]
fn transform_mul_power() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let x = ctx.var("x");
let two = ctx.num(2);
let three = ctx.num(3);
let pow = ctx.pow(x, two);
let prod = ctx.mul(&[pow, three]);
let result = transform(&ctx, prod, |a| match a.node() {
AtomNode::Num(2) => Some(ctx.num(7)),
_ => None,
});
assert_eq!(result.to_string(), "(x^7)*3");
}
}