fn part2(input: String) -> Result<usize> {
    let (rules, molecule) = parse_input(&input)?;

    let mut atom_map = HashMap::new();
    atom_map.insert("e", 0);
    let mut rules = rules
        .into_iter()
        .map(|(from, into)| {
            let from = string_to_molecule(&from, &mut atom_map)?;
            if from.len() != 1 {
                return Err(Error::Input(
                    "mapping can only be from one atom to a molecule",
                ));
            }
            let into = string_to_molecule(&into, &mut atom_map)?;
            Ok((into, from[0]))
        })
        .collect::<Result<Vec<_>>>()?;
    rules.sort_by(|(a, _), (b, _)| b.len().cmp(&a.len()));
    let molecule = string_to_molecule(&molecule, &mut atom_map)?;

    let mut astar = crate::astar::AStar::new();
    astar
        .solve(
            molecule,
            |molecule| {
                let mut candidates = arrayvec::ArrayVec::<[Molecule; 32]>::new();
                for (into, from) in &rules {
                    let idx = match find_substr(molecule.as_slice(), into) {
                        Some(idx) => idx,
                        None => continue,
                    };
                    let mut new_molecule = Molecule::with_capacity(molecule.len() + 1 - into.len());
                    new_molecule.extend_from_slice(&molecule[0..idx]);
                    new_molecule.push(*from);
                    new_molecule.extend_from_slice(&molecule[idx + into.len()..]);
                    candidates.push(new_molecule);
                }
                candidates.into_iter().map(|a| (a, 1))
            },
            |molecule| molecule.len().max(1) - 1,
            |molecule| molecule.len() == 1 && molecule[0] == 0,
        )
        .map(|path| path.last().unwrap().1)
        .ok_or(Error::Input("no solution found for input"))
}
// The way part2 is made to run efficiently is a combination of
// three concepts. The first key point is that instead of working
// forward towards the molecule, the problem is reversed, and looks
// at what molecules could be turned into the desired molecule.
//
// Secondly, since the shortest amount of steps is required, it
// uses the A* algorithm, with the current molecule length as a
// heuristic for how far off it is from reaching the starting
// point of a single electron.
// 
// Finally, it simplifies the problem slightly by removing all
// the string processing from the actual part that does work, and
// instead assigns every atom a unique number that also has a one
// byte footprint.
//
// On my particular input, each molecule on average had 11.5 ways
// it could've been produced (out of the 43 maximum). A naive BFS
// would lead towards 11.5^n expansions, A* on the other hand can
// behave closer to a DFS when it's on the right track.
The way part2 is made to run efficiently is a combination of
three concepts. The first key point is that instead of working
forward towards the molecule, the problem is reversed, and looks
at what molecules could be turned into the desired molecule.

Secondly, since the shortest amount of steps is required, it
uses the A* algorithm, with the current molecule length as a
heuristic for how far off it is from reaching the starting
point of a single electron.

Finally, it simplifies the problem slightly by removing all
the string processing from the actual part that does work, and
instead assigns every atom a unique number that also has a one
byte footprint.

On my particular input, each molecule on average had 11.5 ways
it could've been produced (out of the 43 maximum). A naive BFS
would lead towards 11.5^n expansions, A* on the other hand can
behave closer to a DFS when it's on the right track.
The way part2 is made to run efficiently is a combination of three concepts . The first key point is that instead of working forward towards the molecule the problem is reversed and looks at what molecules could be turned into the desired molecule . Secondly since the shortest amount of steps is required it uses the A * algorithm with the current molecule length as a heuristic for how far off it is from reaching the starting point of a single electron . Finally it simplifies the problem slightly by removing all the string processing from the actual part that does work and instead assigns every atom a unique number that also has a one byte footprint . On my particular input each molecule on average had 11 . 5 ways it could ' ve been produced ( out of the 43 maximum ) . A naive BFS would lead towards 11 . 5^n expansions A * on the other hand can behave closer to a DFS when it ' s on the right track .
fn part2(input: String) -> Result<usize> {
    let (rules, molecule) = parse_input(&input)?;
    let mut atom_map = HashMap::new();
    atom_map.insert("e", 0);
    let mut rules = rules
        .into_iter()
        .map(|(from, into)| {
            let from = string_to_molecule(&from, &mut atom_map)?;
            if from.len() != 1 {
                return Err(Error::Input(
                    "mapping can only be from one atom to a molecule",
                ));
            }
            let into = string_to_molecule(&into, &mut atom_map)?;
            Ok((into, from[0]))
        })
        .collect::<Result<Vec<_>>>()?;
    rules.sort_by(|(a, _), (b, _)| b.len().cmp(&a.len()));
    let molecule = string_to_molecule(&molecule, &mut atom_map)?;
    let mut astar = crate::astar::AStar::new();
    astar
        .solve(
            molecule,
            |molecule| {
                let mut candidates = arrayvec::ArrayVec::<[Molecule; 32]>::new();
                for (into, from) in &rules {
                    let idx = match find_substr(molecule.as_slice(), into) {
                        Some(idx) => idx,
                        None => continue,
                    };
                    let mut new_molecule = Molecule::with_capacity(molecule.len() + 1 - into.len());
                    new_molecule.extend_from_slice(&molecule[0..idx]);
                    new_molecule.push(*from);
                    new_molecule.extend_from_slice(&molecule[idx + into.len()..]);
                    candidates.push(new_molecule);
                }
                candidates.into_iter().map(|a| (a, 1))
            },
            |molecule| molecule.len().max(1) - 1,
            |molecule| molecule.len() == 1 && molecule[0] == 0,
        )
        .map(|path| path.last().unwrap().1)
        .ok_or(Error::Input("no solution found for input"))
}
fn part2 ( input : String ) -> Result < usize > { let ( rules , molecule ) = parse_input ( & input ) ? ; let mut atom_map = HashMap :: new ( ) ; atom_map . insert ( " " , 0 ) ; let mut rules = rules . into_iter ( ) . map ( | ( from , into ) | { let from = string_to_molecule ( & from , & mut atom_map ) ? ; if from . len ( ) != 1 { return Err ( Error :: Input ( " " , ) ) ; } let into = string_to_molecule ( & into , & mut atom_map ) ? ; Ok ( ( into , from [ 0 ] ) ) } ) . collect :: < Result < Vec < _ > > > ( ) ? ; rules . sort_by ( | ( a , _ ) , ( b , _ ) | b . len ( ) . cmp ( & a . len ( ) ) ) ; let molecule = string_to_molecule ( & molecule , & mut atom_map ) ? ; let mut astar = crate :: astar :: AStar :: new ( ) ; astar . solve ( molecule , | molecule | { let mut candidates = arrayvec :: ArrayVec :: < [ Molecule ; 32 ] > :: new ( ) ; for ( into , from ) in & rules { let idx = match find_substr ( molecule . as_slice ( ) , into ) { Some ( idx ) => idx , None => continue , } ; let mut new_molecule = Molecule :: with_capacity ( molecule . len ( ) + 1 - into . len ( ) ) ; new_molecule . extend_from_slice ( & molecule [ 0 .. idx ] ) ; new_molecule . push ( * from ) ; new_molecule . extend_from_slice ( & molecule [ idx + into . len ( ) .. ] ) ; candidates . push ( new_molecule ) ; } candidates . into_iter ( ) . map ( | a | ( a , 1 ) ) } , | molecule | molecule . len ( ) . max ( 1 ) - 1 , | molecule | molecule . len ( ) == 1 && molecule [ 0 ] == 0 , ) . map ( | path | path . last ( ) . unwrap ( ) . 1 ) . ok_or ( Error :: Input ( " " ) ) }
The way part2 is made to run efficiently is a combination of
three concepts.
The way part2 is made to run efficiently is a combination of three concepts .
input
String