Bash Count Semi Primes
count_semi_primes() {
    local _n=$1
    local -n _pArr="$2"
    local -n _qArr="$3"
    local -n _outArr="$4"
    local -a _is_prime _semi _cum
    local _i _k _idx
    for ((_i = 0; _i <= _n; _i++)); do _is_prime[_i]=1; _semi[_i]=0; done
    for ((_i = 2; _i * _i <= _n; _i++)); do
        if (( _is_prime[_i] )); then
            for ((_k = _i * _i; _k <= _n; _k += _i)); do
                _is_prime[_k]=0
            done
        fi
    done
    for ((_k = 2; _k * _k <= _n; _k++)); do
        if (( _is_prime[_k] )); then
            for ((_i = 2; _i * _k <= _n; _i++)); do
                if (( _is_prime[_i] )); then
                    _semi[_k * _i]=1
                fi
            done
        fi
    done
    _cum[0]=${_semi[0]}
    for ((_i = 1; _i <= _n; _i++)); do
        _cum[_i]=$(( _cum[_i-1] + _semi[_i] ))
    done
    _outArr=()
    for _idx in "${!_pArr[@]}"; do
        local _lo=${_pArr[$_idx]}
        local _hi=${_qArr[$_idx]}
        _outArr[_idx]=$(( _cum[_hi] - _cum[_lo-1] ))
    done
}

This precomputes semiprimes and prefix sums so each range query becomes a quick subtraction.