bashlistsparse-matrix

HackerRank Bill division: Time limit exceeded in Bash


I'm playing on HackerRank, and I'd like to solve the Bill division exercise in BASH. It is a really simple problem, I've already solved it in multiple languages.

My initial Bash solution was this:

#!/bin/bash

billDivision() {
    local n="${1}"
    local k="${2}"
    local b_charged="${3}"
    shift 3
    local bills=( "${@}" )
    
    (( b_actual = -bills[ k ] ))
    for bill in "${bills[@]}"; do
        (( b_actual += bill ))
    done
    (( b_actual /= 2 ))
    (( b_overcharge = b_charged - b_actual ))
    if(( b_overcharge <= 0 )); then
        echo "Bon Appetit"
    else
        echo "${b_overcharge}"
    fi
}

read nk
n="$( echo "${nk}" | cut -d " " -f 1 )"
k="$( echo "${nk}" | cut -d " " -f 2 )"
read ar
IFS=" "; read -ra arr <<< "${ar}"
read b
result="$( billDivision "${n}" "${k}" "${b}" "${arr[@]}" )"
echo "${result}"

It works for all tests except 4 and 5, which fail with Time limit exceeded. I know that Bash uses sparse arrays with some linked-list-like internal representation that is not optimal for speed of access. So I thought, the reason must be that the array is too long in these cases. I checked one of them, and it confirms my assumption. Therefore, I tried to get rid of the array by iterating over the elements of a space separated string. I even inlined the function:

#!/bin/bash

read nk
n="$( echo "${nk}" | cut -d " " -f 1 )"
k="$( echo "${nk}" | cut -d " " -f 2 )"
read bills
read b_charged

(( counter = 0 ))
for bill in ${bills}; do
    if(( counter++ != k )); then
        (( b_actual += bill ))
    fi
done
(( b_actual /= 2 ))
(( b_overcharge = b_charged - b_actual ))
if(( b_overcharge <= 0 )); then
    echo "Bon Appetit"
else
    echo "${b_overcharge}"
fi

I got the same result, time limit exceeded in test cases 4 and 5.

What is the trick to make Bash process long lists faster? Or fast enough for HackerRank?


Solution

  • You're wasting time adding up bills. This should work fine:

    read n k
    read -a bill
    read charged
    IFS=+
    actual=$(((${bill[*]}-bill[k])/2))
    if ((charged == actual)); then
      echo Bon Appetit
    else
      echo $((charged-actual))
    fi