#[inline]
pub fn split_state_id_groups<S>(
    states: &[S],
    state_ids_by_group_id: &mut Vec<Vec<u32>>,
    group_ids_by_state_id: &mut [u32],
    start_group_id: u32,
    mut should_split: impl FnMut(&S, &S, &[u32]) -> bool,
) -> bool {
    let mut result = false;

    // Use a flat bitset instead of Vec::contains for O(1) membership tests.
    let mut is_split = vec![false; states.len()];

    let mut group_id = start_group_id;
    while (group_id as usize) < state_ids_by_group_id.len() {
        let state_ids = &state_ids_by_group_id[group_id as usize];
        let mut split_state_ids = Vec::new();

        let mut i = 0;
        while i < state_ids.len() {
            let left_state_id = state_ids[i] as usize;
            if is_split[left_state_id] {
                i += 1;
                continue;
            }

            let left_state = &states[left_state_id];

            // Identify all of the other states in the group that are incompatible with
            // this state.
            let mut j = i + 1;
            while j < state_ids.len() {
                let right_state_id = state_ids[j] as usize;
                if is_split[right_state_id] {
                    j += 1;
                    continue;
                }
                let right_state = &states[right_state_id];

                if should_split(left_state, right_state, group_ids_by_state_id) {
                    split_state_ids.push(right_state_id as u32);
                    is_split[right_state_id] = true;
                }

                j += 1;
            }

            i += 1;
        }

        // If any states were removed from the group, add them all as a new group.
        if !split_state_ids.is_empty() {
            result = true;
            state_ids_by_group_id[group_id as usize].retain(|i| !is_split[*i as usize]);

            let new_group_id = state_ids_by_group_id.len() as u32;
            for id in &split_state_ids {
                group_ids_by_state_id[*id as usize] = new_group_id;
                is_split[*id as usize] = false;
            }

            state_ids_by_group_id.push(split_state_ids);
        }

        group_id += 1;
    }

    result
}
