Solving Codility exercises in shell script
30 Jun 2016This article examines the process of solving Codility programming challenges using Bash scripting. While Bash may not be the most intuitive choice for algorithmic problems, it offers an interesting perspective on problem-solving and can help improve one’s command of shell scripting.
The first exercise is the binary gap. Main job is done by bc
calculator. Take away from this is the horrible pattern for dealing with trailing characters
function convert {
# convert to binary
local b=$( echo "obase=2;$1" | bc )
# remove trailing spaces
local c="${b%"${b##*[!0]}"}"
# split by 1s and run high water mark algorithm
local i r=0
for i in ${c//1/ } ; do
l=${#i} && (( l > r )) && (( r=l ))
done
echo "$r"
}
convert 20
convert 1041
convert 2043403647
The second exercise is quite a bit of pain. Passing arrays into and from functions is complex. I unwrap the values when calling the function and collect it back after shifting away the first argument. Clearly it won’t work for functions that accept multiple arrays as arguments. The algorithm by itself is trivial though.
function rotate {
# get the first element and shift as array is passed 'unwrapped'
local k="${1}"
shift
# wrap back the array and get its length
local a=("${@}")
local n="${#i[@]}"
# calculate the split, swap sides
(( k = n - k % n ))
local p=("${a[@]:0:k}")
local q=("${a[@]:k}")
local r=("${q[@]}" "${p[@]}")
echo "${r[@]}"
}
i=(3 8 9 7 6)
rotate 3 "${i[@]}"
i=("dude" "there" "you go")
rotate 2 "${i[@]}"
The third exercise is trivial once you know the trick that folding arrays with xor
gives you the xor
fold of unpaired elements. The logic translates directly into shell script.
function pair {
local i r=0
for i in "$@" ; do
(( r ^= i ))
done
echo "$r"
}
pair 9 3 9 3 9 7 9
The Frog Jump Exercise is another application of (( ... ))
magic. Very little is learned except that local x=1 y=$x
will not work in bash, and to see those kind of errors always run your bash scripts with set -o nounset
.
function jump {
local a=$1 b=$2 c=$3
local d=$(( b - a ))
local j=$(( d / c ))
(( j * c == d )) || (( j += 1 ))
echo "$j"
}
jump 10 85 30
The Tape Equilibrium Exercise is where we discover that we don’t have abs
function in shell (in korn shell we do though).
function balance {
local b=0 a=0 i d
# compute the total weight of the bar
for i in "$@" ; do
(( b += i ))
done
# set max to unbalanced (0 -- all) distribution
local m=$b
for i in "$@" ; do
# move a piece from left to right
(( a += i )) && (( b -= i ))
# compute the absolute difference
(( b > a )) && (( d = b - a )) || (( d = a - b ))
# update low water mark
(( d < m )) && (( m = d ))
done
echo "$m"
}
balance 3 1 2 4 3
To solve Permutation Missing Element Exercise we can use the fact that the sum of arithmetic progression is a closed form expression and can be computed in O(1). If we compare this theoretical sum to the actual sum of the array the difference will give us the missing argument.
function missing {
local s=0 n="$#" i
for i in "$@" ; do
(( s += i ))
done
echo $(( (n + 2) * (n + 1) / 2 - s ))
}
missing 2 3 1 5
The Frog River One kind of pushes us towards using proper ADTs, in this case hash maps. I cheated a bit here storing numbers in a :
separated string a
. As this datatype doesn’t track the size of our ‘list’, we better store the size in an additional state variable w
.
function wait {
# get the width of the river
local x=$1
shift
# a is a list :1:6:7: with O(n) for membership check
local i a=':' d c=0 b w=0
for i in "$@" ; do
if [[ "$a" != *":${i}:"* ]] ; then
# add item to list
a="${a}${i}:"
(( w += 1 ))
(( w == x )) && { echo "$c" ; return ; }
fi
(( c += 1 ))
done
}
wait 5 1 3 1 4 2 3 5 4
Another Permutations Exercise, another cheat :). I just sort the values, which is O(n * log n) operation and check if the sorted order contains gaps. It’s not as fast as O(n) but definitely better than naive repeated linear scanning which is O(n * n).
function permchk {
local i local a=$( echo "$@" | tr " " "\n" | sort | tr "\n" " " ) c=1
for i in $a ; do
(( c == i )) && (( c += 1 )) || { echo false ; return ; }
done
echo true
}
permchk 4 3 1 2
permchk 4 1 3
The next Missing Integer Exercise is pretty much the variation to the theme, just add uniqueness to sort -u
to skip over duplicates
function findmiss {
local i local a=$( echo "$@" | tr " " "\n" | sort -u | tr "\n" " " ) c=1
for i in $a ; do
(( c == i )) && (( c += 1 )) || { echo $c ; return ; }
done
}
findmiss 1 3 6 4 1 2
So far the ugliest solution for Max Counts Exercise where I try to use fixed length delimited string as poor man’s array. The thorny path led me to few realizations. Firstly, {1..n}
expansions don’t “understand” variables therefore {1..$n}
won’t work. Secondly, there’s no simple way to repeat a string multiple times like "hello" * 5
in Python. I had to write a function instead. Thirdly, it’s not so easy to work with strings as arrays as constant padding and trimming is not fun and adds significant clutter, so I probably gonna pass on this “data structure” in the future.
readonly W=5
# repeat string $1 $2 times
function rep {
local a="" i
for (( i = 0 ; i < $2 ; i++ )) ; do
a="$a$1"
done
echo "$a"
}
# pad the value with spaces from left
function pad {
printf "%${W}s" $1
}
function count {
local n=$1
local m=$( pad 0 )
# initialize array "....0....0....0....0"
local a=$( rep "$m" $n )
shift
local i v w b c
for i in "$@" ; do
if (( i > n )) ; then
# refill with max
a=$( rep "$m" $n )
else
(( b = (i - 1) * W ))
(( c = i * W ))
# the ugly bit, update the counter, and pad it again
v=$( pad $(( ${a:b:W} + 1 )) )
(( v > m )) && m=$v
a="${a:0:b}${v}${a:c}"
fi
echo "$a"
done
}
count 5 3 4 4 6 1 4 4
Following was another Counting Exercise. The solution is trivial, adding the code for completeness’ sake.
function pair {
local i s=0 r=0
for i in "$@" ; do
(( s += i ))
done
for i in "$@" ; do
(( i == 0 )) && (( r += s )) || (( s-- ))
done
echo "$r"
}
pair 0 1 0 1 1
After that a simple exercise in rounding up and down. Move the a
right to the nearest number divisible by c
, move the b
left to the nearest number divisible by c
. Compute the number of intervals fitting in between and add one to count the dots.
function count {
local a=$1 b=$2 c=$3 n=0
(( a % c == 0 )) || (( a = (a / c + 1) * c ))
(( b = (b / c) * c ))
echo $(( (b - a) / c + 1 ))
}
count 6 11 2 # 6 8 10
count 6 10 2 # 6 8 10
count 7 12 3 # 9 12
At one point I’ll need to start using proper data types such as hash map. Well, maybe next time. For Genomic Range Query I’ve just created 4 arrays for folds, which will do for now, but are clearly very verbose. Learnings from this exercise is that looping through characters in a sting needs a a function call to scan
, array concatenation +=(1)
is very different from +=1
, where the second expression just quietly promotes (demotes?) array to string, and that && || && ||
chains don’t always work for long statements with arithmetic context (( ))
. I will need to explore more on the last point.
function nucleo {
local n=$1
shift
local a=0 c=0 g=0 t=0
declare -a as=(0) cs=(0) gs=(0) ts=(0)
for i in $( echo "$n" | fold -w1 ) ; do
case "$i" in
A) (( a++ )) ;;
C) (( c++ )) ;;
G) (( g++ )) ;;
T) (( t++ )) ;;
esac
as+=($a) ; cs+=($c) ; gs+=($g) ; ts+=($t)
done
declare -a b vs
for i in "$@" ; do
b=( ${i//:/ } )
p=${b[0]}
q=$(( ${b[1]} + 1 ))
if (( as[q] - as[p] > 0 )) ; then vs+=(1)
elif (( cs[q] - cs[p] > 0 )) ; then vs+=(2)
elif (( gs[q] - gs[p] > 0 )) ; then vs+=(3)
else vs+=(4)
fi
done
echo "${vs[@]}"
}
nucleo CAGCCTA 2:4 5:5 0:6
By the way we probably don’t need ts
as we infer existence of T
s indirectly in the else branch.
Next exercise is as boring as the rest of summation exercises. The jobs is greatly obscured by the syntax. Learnings from this one are none, except I am getting less and less gotchas with bash arrays.
function minavg {
# scan, use s as threshold
declare a=(0)
local i s=0
for i in "$@" ; do
(( s+= i ))
a+=($s)
done
# do not divide, work with the average multiplied by 6
local v r
for (( i = 2 ; i < ${#a[@]} ; i++ )) {
(( v = (a[i] - a[i - 2]) * 3 ))
(( v < s )) && (( s = v )) && (( r = i - 2 ))
if (( i > 2 )) ; then
(( v = (a[i] - a[i - 3]) * 2 ))
(( v < s )) && (( s = v )) && (( r = i - 3 ))
fi
}
echo "$r"
}
minavg 4 2 2 5 1 5 8
The triangle exercise is actually the first one making bash shine … a bit. Making an array out of an input string and sort it numerically in one line is not bad at all. Took me literally 2 minutes to solve it, especially because they’ve put O(n log n)
into expected complexity. As one of my professors used to say if your math teacher asks you ‘which number’ the right answer is almost always “0”, sometimes “1”.
function triangle {
local i v
declare -a t r=()
for i in "$@" ; do
t=($( echo "$i" | tr ":" "\n" | sort -n | tr "\n" " " ))
r+=($(( t[0] + t[1] > t[2] )))
done
echo "${r[@]}"
}
triangle 10:2:5 1:8:20 1:1:1
Followed up by another short exercise leveraging sort -u
. Nothing learned, except maybe that uniq
compares adjacent lines only, so there’s no way to avoid explicit sorting.
function distcount {
declare -a v=($( echo "$@" | tr " " "\n" | sort -u | tr "\n" " " ))
echo "${#v[@]}"
}
distcount 2 1 1 2 3 1
And another one aiming at the same “skill”.
function maxprod {
declare -a i=($( echo "$@" | tr " " "\n" | sort -n | tr "\n" " " ))
local a b n=${#i[@]}
(( a = i[0] * i[1] * i[n - 1] ))
(( b = i[n -3] * i[n - 2] * i[n - 1] ))
(( a > b)) && echo "$a" || echo "$b"
}
maxprod -3 1 2 -2 5 6
At this point I have to admin I don’t commit nearly as many syntactic errors as before. So let’s keep going and see where it’s gonna take me.
I spent quite some time understanding the next exercise but decided to cheat a bit and take someone else’s solution. Interestingly bash really excelled in this exercise. The complete solution is not much longer than Luca’s python code. Learnings are numerous. Firstly, you can pipe the loops into construct thus avoiding storage. Secondly, pipes operate with their own namespace so assigning an outer variable from within piped code is horribly complex. Thirdly, once in pipes, stay in pipes. Still, I don’t know how to reference input array $@
from arithmetic context (( ))
, will have to explore it later.
function discint {
declare -a a=("$@")
local i b c
for (( i = 0 ; i < ${#@} ; i++ )) ; do
echo $(( i - a[i] )) "\t1"
echo $(( i + a[i] )) "\t-1"
done | grep -v "^$" | sort -k1,1n -k2,2rn | while read _ i ; do
(( i > 0 )) && (( b += c ))
(( c += i ))
echo "${b}"
done | tail -1
}
discint 1 5 2 1 4 0
And after the last high another low with the fish. Considerable amount of time is spent bending the data structures around to reduce the amount of clutter. I took a longer path to create a pipeable function. Still hasn’t figured it out how to better return values from a pipe, other than by tail -1
. Another pain is adding things to an empty array. There must be a better way than checking -z "${s+_}"
. This experience reminded me of my time at the university where all our code was highly optimized to make it run faster by completely disregarding readability of the code and using contra-intuitive data types.
function unwrap {
tr ' :' "\n\t" | while read -r w d ; do
(( d == 1 )) && echo $(( w * -1 )) || echo "$w"
done
}
function fish {
declare -a s=()
local i a l=0
unwrap | while read i ; do
# push new fish to stack be default
a=true
while (( s[0] < 0 )) ; do
if (( s[0] * -1 > i )) ; then
# our upstream fish was eaten by the top of the stack
a=false ; break
else
# top of the stack was eaten, reduce count and pop
(( l-- )) ; s=("${s[@]:1}")
fi
done
# adding new fish to the stack
if [ $a == true ] ; then
(( l++ ))
[ -z "${s+_}" ] && s=($i) || s=("$i" "${s[@]}")
fi
# echo current length
echo "$l"
done | tail -1
}
echo 4:0 3:1 2:0 1:0 5:0 | fish
Followed by another stack exercise. Again learnings are to use data structures that reduce the need to do operations in bash, like translating brackets to numbers with offset of 3, which lets us easily match opening and closing brackets in one operation.
function brackets {
tr "{[(}])" "123456" | fold -w1 | while read c ; do
if (( c < 4)) ; then
[ -z "${s+_}" ] && s=($c) || s=($c "${s[@]}")
else
(( c - s[0] == 3 )) && s=("${s[@]:1}") || break
fi
[ -z "${s+_}" ] && echo true || echo false
done | tail -1
}
echo "{[()()]}" | brackets
echo "([)()]" | brackets
Next one was quite interesting, as it required a bit of set-theoretic proof to bi-directionally map two sets, thus reducing the problem to a much simpler one. Learning is really just one, but important one, you can create an initializing block inside of piped coded hiding it behind some sort of [ -z "${s+_}" ]
test. Getting more and more used to pipes and stuff.
function wall {
tr " " "\n" | while read h ; do
[ -z "${s+_}" ] && { declare -a s=() ; local n=0 c=0 ; }
while (( n > 0 )) && (( s[n-1] > h )) ; do (( n-- )) ; done
if (( n == 0 )) || (( s[n-1] != h )) ; then
(( c++ ))
s[n]=${h}
(( n++ ))
fi
echo "$c"
done | tail -1
}
echo 8 8 5 7 9 8 7 4 8 | wall
The EquiLeader is easier to solve if we recognize that if a number dominates two parts then is will dominate the complete sequence. Therefore we compute the dominating number and count it in as $s
and then run through sequence checking the balances
function eqlead {
declare -a s=($( echo "$@" | tr " " "\n" | sort | uniq -c | sort -k1,1nr | head -1 ))
local i a=0 b=$# l=0 r="${s[0]}"
for i in "$@" ; do
(( a++ )) ; (( b-- ))
if (( i == s[1] )) ; then
(( l++ )) ; (( r-- ))
fi
(( l * 2 > a )) && (( r * 2 > b )) && echo $(( a - 1 ))
done
}
eqlead 4 3 4 4 4 2
The Dominator should probably come before EquiLeader. Nothing interesting really, except tried not to loop over things explicitly and use pipes as much as possible. Still need to capture intermediate results with $()
because I don’t see any nice way of branching and merging the pipes althogh this probably points in the right direction. Another surprise is that uniq -c
does some unexpected padding on counter column therefore I am not sure how to cut -f1
out of results and have to go with read -r n _
. By the way read -r n
would capture complete lines
function dominator {
declare -a c=($( tr ' ' "\n" \
| sort \
| uniq -c \
| sort -k1,1nr \
| while read -r n _ ; do echo "$n" ; done \
| tr "\n" ' ' ))
local s=$( echo "${c[@]}" | tr ' ' '+' | bc )
echo $(( c[0] * 2 > s ))
}
echo 3 4 3 2 3 -1 3 3 | dominator
While looking for solving the next one I discovered a strangely popular topic among bash programmers: how to read the first and the last lines from a file. For documentation purposes { IFS= read -r a ; echo "$a" ; tail -1 ; }
worked for me. The algorithm is straightforward. If the new value is below running minimum, update the minimum, otherwise check if trading at this point would increase running max profit, and update it if required.
function mprofit {
local i m=$1 b=0
for i in "${@:1}" ; do
if (( i > m )) ; then
(( i - m > b )) && (( b = i - m ))
else
(( m = i ))
fi
done
echo "$b"
}
mprofit 23171 21011 21123 21366 21013 21367
Next task made me realize I have to write routines to reverse arrays myself. Implemented Kadane’s algorithm.
function kadane {
declare -a a=("$@") b=(0)
local i n=$# c=0 d=0
for (( i = 1 ; i < n - 1 ; i++ )) ; do
(( c + a[i] > 0 )) && (( c += a[i] )) || (( c = 0 ))
(( d < c )) && (( d = c ))
b+=($d)
done
b+=(0)
echo "${b[@]}"
}
function rev {
local i
declare -a a=()
for i in "$@" ; do [ -z "${a+_}" ] && a=($i) || a=($i "${a[@]}") ; done
echo "${a[@]}"
}
function mdslice {
declare -a p=($( kadane "$@" )) q=($( rev $( kadane $( rev "$@" ) )))
local n=$# m=0 s
for (( i = 1 ; i < n - 1 ; i++ )) {
(( s = p[i-1] + q[i+1] ))
(( s > m )) && (( m = s ))
}
echo "$m"
}
mdslice 3 2 6 -1 4 5 -1 2
Skipping the max slice sum as this is just a simpler version of the last one.
Following are a set of exercises that I expect will make me use bc
a lot. The first one is about finding integer square root.
function minrec {
local i n=$( echo "scale=0;sqrt($1)" | bc ) a=$1
for (( i = n ; i > 0 ; i-- )) ; do
(( a % i == 0 )) && { echo $(( 2 * (i + (a / i)) )) ; break ; }
done
}
minrec 30
And the following Count Factors Exercise is pretty much the same loop, just a different counter and a different break condition.
function nfactors {
local i n=$( echo "scale=0;sqrt($1)" | bc ) a=$1 r=0
# set the counter to -1 if a is a square to not double count sqrt(a)
(( n * n == a )) && (( r = -1 ))
for (( i = n ; i > 0 ; i-- )) ; do
(( a % i == 0 )) && (( r += 2 ))
done
echo "$r"
}
nfactors 24
For the Peaks Exercise I just took someone else’s code, as it was a fairly uninspiring problem. I’ve added comments to the logic, one interesting nuance of the algorithm is the part with integer divisions in c / i > f
and c / i == f
used to check if a peak falls within current group. Very nice indeed. No new bash gotchas.
function peaks {
local n=$# i f g s c
declare -a a=("$@") p=()
# compute all the peaks
for (( i = 2 ; i < n - 1; i++ )) ; do
(( a[i-1] < a[i] )) && (( a[i] > a[i+1] )) && p+=($i)
done
for (( i = 1 ; i <= n ; i++ )) ; do
# for all group sizes i that evenly divide n
(( n % i != 0 )) && continue
# number of peaks found f, number of groups g, success s
f=0 ; g=$(( n / i )) ; s=1
for c in "${p[@]}" ; do
# overshot, breaking out
(( c / i > f )) && { s=0 ; break ; }
# falls into the group, increase counter
(( c / i == f )) && (( f++ ))
done
# if found less than g set success status to 0
(( f != g )) && s=0
# super successful exit
(( s == 1 )) && { echo "$g" ; break; }
done
}
peaks 1 2 3 4 3 4 1 2 3 4 6 2
The count semi-primes exercise is trivial but it’s nice to play with memoization in bash. I’ve added two indices PRIMES_INDEX
and PRIMES
containing respectively indices of prime numbers to quickly answer a question if a number is a prime and to loop quickly through prime numbers.
declare -a PRIMES_INDEX=(0 0 1 1 0 1) PRIMES=(2 3 5)
P_CURRENT=5
function primes {
local b=$1 f p
while (( P_CURRENT * P_CURRENT <= b )) ; do
(( P_CURRENT++ ))
f=1
for p in "${PRIMES[@]}" ; do
(( P_CURRENT % p == 0 )) && { f=0 ; break; }
done
(( PRIMES_INDEX[P_CURRENT] = f ))
(( f == 1 )) && PRIMES+=($P_CURRENT)
done
echo "${PRIMES[@]}"
}
function semipr {
local i j c p
for i in "$@" ; do
r=(${i//:/ })
(( c = 0 ))
primes "${r[1]}"
for (( j = r[0] ; j <= r[1] ; j++ )) ; do
for p in "${PRIMES[@]}" ; do
(( p * p > j )) && break;
if (( j % p == 0 )) && (( PRIMES_INDEX[j / p] == 1 )) ; then
(( c++ ))
break
fi
done
done
echo "$c"
done
}
semipr 1:26 4:10 16:20 12:2035
Very much the same applies for the count non divisible, it makes perfect sense to create indices during linear scan to avoid search later on. In this case we needed an index of occurences os
which for every number i
gives you the number of times os[i]
the number i
occurs in the input array. And the array cs
is the number of non divisors which is uniformely set to n
at the beginning, and then reduce while we loop over divisors.
function nondiv {
declare -a cs=() os=() rs=()
local i=0 n=$# j d
while (( i < 2 * n )) ; do (( os[i++]=0 )) ; done
for a in "$@" ; do (( os[a]++ )) ; done
(( i = 0 )) ; while (( i < 2 * n )) ; do (( cs[i++] = n )) ; done
for (( d = 1 ; d < 2 * n ; d++ )) ; do
(( os[d] == 0 )) && continue
for (( j = d ; j < 2 * n ; j += d )) ; do
(( cs[j] -= os[d] ))
done
done
for a in "$@" ; do
rs+=("${cs[$a]}")
done
echo "${rs[@]}"
}
nondiv 3 1 2 3 6
Followed by chocolates by numbers, where you have two different velocities around circle, m
and n
. Let’s linearize positions. So a[i+1] = a[i] + n
. To check that two linearized positions represent the same place on the circle we wrap around m
, in other words steps p
and q
represent the same position on the circle if a[p] % m == a[q] % m
or (a[p] - a[q]) % m == 0
. And for the same two positions to be on the same progression we need the difference to be divisible by n
as well (a[p] - a[q]) % n == 0
.
function gcd {
local a=$1 b=$2 c
while (( a % b != 0 )) ; do
c=$a ; a=$b ; (( b = c % b ))
done
echo "$b"
}
function choc {
local n=$1 m=$2 d=$( gcd "$1" "$2" )
echo $(( n / d ))
}
choc 10 4
The common prime divisors is interesting because of its reduce
function. GCD of two numbers should be all you need in order to reduce the numbers. So given initial value a
and gcd
, the a / gcd
should be a product of divisors of the very same gcd
. So we extract common part from a / gcd
and gcd
and reduce a / gcd
by this amount. We proceed till either the number is reduced to 1 or cannot be reduced any further.
function gcd {
local a=$1 b=$2 c
while (( a % b != 0 )) ; do
c=$a ; a=$b ; (( b = c % b ))
done
echo "$b"
}
function reduce {
local a=$1 g=$2 f d r=1
# first division
(( f = a / g ))
# need to be reduced as f is not a divisor of g
while (( g % f != 0 )) ; do
# find the amount the f needs to be reduced by
d=$( gcd "$g" "$f" )
# check if can be reduced
(( d == 1 )) && { r=0 ; break ; } || (( f /= d ))
done
echo "$r"
}
function cpd {
local i a b g p q v
declare -a r
for i in "$@" ; do
r=(${i//:/ }) ; a="${r[0]}" ; b="${r[1]}"
g=$( gcd "$a" "$b" )
p=$( reduce "$a" "$g" ) ; q=$( reduce "$b" "$g" )
(( p == 1 )) && (( q == 1 )) && (( v++ ))
done
echo "$v"
}
cpd 15:75 10:30 3:5 22:7744 44:23232
The ladder is a counting exercise which is just implementation of Fibonacci numbers. Pretty much the only complexity is to understand that the number of paths leading to n
-th rung is fib(n)
.
declare -a FIB=(1 1 2)
declare FIB_I=2
function fib {
local n=$1 f
while (( FIB_I < n )) ; do
(( FIB_I++ ))
(( f = FIB[FIB_I - 2] + FIB[FIB_I - 1] ))
FIB+=($f)
done
echo "${FIB[$n]}"
}
function lad {
declare -a r
local a b i f d
for i in "$@" ; do
r=(${i//:/ }) ; a="${r[0]}" ; b="${r[1]}"
f=$( fib "$a" )
d=$( echo "2^$b" | bc )
echo $(( f % d ))
done
}
lad 4:3 4:2 5:4 5:3 1:1
The Fibonacci frog is actually dynamic programming exercise, which I was too lazy to implement just yet. I am traversing all possible paths instead, which is very bad. When I reach the same point c
on two different paths, I am computing the value function at point c
twice. Nobody should ever do that. Learnings are that writing recursive functions in bash is pain, probably even more pain than writing iterative versions with manual stack handling.
declare -a FIB=(1 2)
declare FIB_I=1
# generate fibonacci numbers up to n
function fib {
local n=$1 f
while (( FIB[FIB_I] <= n )) ; do
(( FIB_I++ ))
(( f = FIB[FIB_I - 2] + FIB[FIB_I - 1] ))
FIB+=($f)
done
}
function jmp {
# c - current position
# d - next position
# f - iterator over fibonacci numbers
# m - length of min path, -1 if no path from current point
# v - the length of sub path starting from d = c + f
local c=$1 d f m v
shift
# n is the length of the padded array
local n=$#
declare -a as=("$@")
# check if we reached the end
if (( c == n - 1 )) ; then
echo "0"
else
m=-1
for f in "${FIB[@]}" ; do
# check if jumping over f is a valid point
(( d = c + f ))
(( d < n )) || continue
(( as[d] == 1 )) || continue
# find the optimal path length starting from d
v=$( jmp $d "$@" )
# update minimum
if (( v >= 0 )) ; then
if (( m == -1 )) || (( v + 1 < m )) ; then
(( m = v + 1 ))
fi
fi
done
echo "$m"
fi
}
function frog {
# pre-compute fibonacci numbers
fib "$#"
# pad the array with terminal 1 to denote terminal position
jmp -1 "$@" 1
}
frog 0 0 0 1 1 0 1 0 0 0 0
Enough.