Rasmus Michelsen Rasmus Michelsen - 2 months ago 15
Bash Question

Recursive calls in bash (catalan-numbers)

Im trying to create a program that lists all catalan-numbers below or equal to an argument in a bash-script. This is what I currently have but its giving me a stackoverflow error (I believe the error must be in the for-loop, but I can't figure out why). I have made this program in java and it works so I think it must be some syntax error?

#!/usr/bin/env bash
pcat=0

Cat() {

res=0

if [ $1 -le 1 ]
then
echo 1
return 1
fi

for ((i=0; i<$1; i++))
do
var1=$(($1-($i+1)))
call1=$(Cat $i)
call2=$(Cat $var1)

res=$(( res+call1+call2 ))
done

echo ${res}
return res

}


while [ $pcat -lt $1 ]
do
Cat $pcat

pcat=$((pcat+1))
done

Answer

The line where you do return res is incorrect, return could deal only with numbers and number less than 128 in general.

Assuming what you meant was return $res, the script will run.

I managed to get the program working with a similar code to yours:

#!/bin/bash
catalan() {
    local n=$1
    #echo "called with $n" >&2

    if (( n <= 1 )); then
        res=1
    else
        res=0
        for ((i=0; i<n; i++))
        do
            var1=$(( n-i-1 ))
            call1=$(catalan $i)
            call2=$(catalan $var1)
            res=$(( res+call1*call2 ));
            #echo ":$i:$var1: result Call1:$call1: and Call2:$call2: $res" >&2
        done
    fi
    #echo "result is ${res}" >&2
    echo "$res"
}

n=$1
until  (( pcat > n ))
do     catalan "$((pcat++))"
done


echo "all was done"

There was a second problem in that the values of Call1 and Call2 need to be multiplied, not added. Changed res+call1+call2 to:

res=$(( res+call1*call2 ))

But the resultant code was very slow. Just to calculate the tenth (10) catalan number the code took 16 seconds.


An entirely new program that keeps the values inside a single array: catarray.

As this:

#!/bin/bash

# some initial values to jump start the code:
catarray=( 1 1 2 5 )

#############################################################################
catalan(){
    #echo "making call for $1" >&2
    local n=$1
    # ${#catarray[@]} is the count of values in catarray (last index + 1).
    # if the number n to be found is not yet in the array of values of
    # catarray then we need to calculate it. Else, we just print the value.
    if (( n >= ${#catarray[@]} )); then
        #echo "$n is bigger than ${#catarray[@]}" >&2
        # this is a new number, lets loop up till we
        # fill the array just up to this value
        for (( i=${#catarray[@]};i<=n;i++)); do
        #echo "fill index $i in array" >&2
        # calculate the sum of all terms for catalan of $n.
            for(( j=0;j<i;j++ )); do
                (( catarray[i] += catarray[j] * catarray[i-j-1] ))
                #echo "done math in $i for $j with ${catarray[j]} *
                #echo "* ${catarray[i-j-1]} = ${catarray[i]}"
            done
        done
    fi
    # After making the math or else we just print the known value.
    #printf 'result of catalan number is %s\n' "${catarray[n]}"
}
#############################################################################
catalan "$1"

printf '%s, ' "${catarray[@]}"; echo

Wich will execute the tenth (10) catalan number in just 4 milliseconds.

A lot of echos were included to "see" how the program works. You may unquote them.

There is a limit though, numbers in bash should fit in 64 bits (for 64 bit computers) or be less than (2^63-1). That makes the biggest catalan number possible the 35th.

$ catalan 35
1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786, 208012, 742900,  
2674440, 9694845, 35357670, 129644790, 477638700, 1767263190,  
6564120420, 24466267020, 91482563640, 343059613650, 1289904147324,  
4861946401452, 18367353072152, 69533550916004, 263747951750360,  
1002242216651368, 3814986502092304, 14544636039226909,  
55534064877048198, 212336130412243110, 812944042149730764,  
3116285494907301262

But will only take ~20 miliseconds to do that.

Comments