Quicksort implemented in shell script doesn't work - bash

I am learning shell scripting and trying to implement quick sort using it.
But it doesn't work, actually it acting weird.
The script:
#!/bin/bash
declare -a data=()
declare -r size=10
declare -i steps=0
for i in $(seq 0 $size); do
data[$i]=$(expr $RANDOM % $size)
done
function partition() {
pivot=${data[$1]}
left=$(expr $1 + 1)
right=$2
while true; do
while [[ $left -le $right && ${data[$left]} -le $pivot ]]; do
left=$(expr $left + 1)
steps=$(expr $steps + 1)
done
while [[ $right -ge $left && ${data[$right]} -ge $pivot ]]; do
right=$(expr $right - 1)
steps=$(expr $steps + 1)
done
if [[ $left -gt $right ]]; then
break
fi
temp=${data[$left]}
data[$left]=${data[$right]}
data[$right]=$temp
done
temp=${data[$1]}
data[$1]=${data[$right]}
data[$right]=$temp
echo $right
}
function quickSort() {
if [[ $1 -lt $2 ]]; then
local partitionPoint=$(partition $1 $2)
quickSort $1 $(expr $partitionPoint - 1)
quickSort $(expr $partitionPoint + 1) $2
fi
}
# involve the algorithm
quickSort 0 $(expr $size - 1)
echo "Steps: $steps"
echo ${data[#]}
I tried to log some variable but it's just weird I can't figure out what's going on.
When I comment out all the code in the two functions and 'manually' update elements of data variable, it did changed.
I tried to log some variables and they all changing.
But the final output remains untouched.
Or maybe it eventually reversed all the flipping but I don't know.
I can't figure it out.
At last I compare my python implementation line by line. No mistakes. But it just not working.
Am I miss something?
Variable scope or something?
Any advice will be appreciated.

There are several smaller issues in this code, but the biggest issue is here:
partitionPoint=$(partition $1 $2)
This is problematic because $( ... ) runs ... in a subshell -- a separate, fork()ed-off process, consequently with its own variable scope.
If you instead return your result via indirect assignment, making it:
partition "$1" "$2" partitionPoint
and inside the function using:
printf -v "$3" %s "$right"
...to assign the value to the variable so named, things work much better.
#!/bin/bash
PS4=':$LINENO+'; set -x
data=()
size=10
steps=0
for ((i=0; i<size; i++)); do
data[$i]=$((RANDOM % size))
done
partition() {
local pivot left right dest temp
pivot=${data[$1]}
left=$(($1 + 1))
right=$2
dest=$3
while true; do
while (( left <= right )) && (( ${data[$left]} <= pivot )); do
left=$(( left + 1 ))
steps=$(( steps + 1 ))
done
while (( right >= left )) && (( ${data[$right]} >= pivot )); do
right=$(( right - 1 ))
steps=$(( steps + 1 ))
done
(( left > right )) && break
temp=${data[$left]}
data[$left]=${data[$right]}
data[$right]=$temp
done
: '$1='"$1" right="$right" 'data[$1]='"${data[$1]}" 'data[$right]='"${data[$right]}"
temp=${data[$1]}
data[$1]=${data[$right]}
data[$right]=$temp
printf -v "$dest" %s "$right"
}
quickSort() {
local partitionPoint
if (( $1 < $2 )); then
partition "$1" "$2" partitionPoint
quickSort "$1" "$(( partitionPoint - 1 ))"
quickSort "$((partitionPoint + 1))" "$2"
fi
}
# involve the algorithm
quickSort 0 "$(( size - 1 ))"
echo "Steps: $steps"
printf '%s\n' "${data[#]}"

Related

Finding the greatest common divisor of two numbers in Bash

I am coding a program that computes the GCD of two numbers. My problem happens in some input cases:
GCD (88, 100) = 4
But my program returns an empty space (like it couldn't get the $gcd), but I haven't really got to the exact problem in my code yet.
#!/bin/bash
while true; do
read a b
gcd=$a
if [ $b -lt $gcd ]; then
gcd=$b
fi
while [ $gcd -ne 0 ]; do
x=`expr $a % $gcd`
y=`expr $b % $gcd`
if [ $x -eq 0 -a $y -eq 0 ]; then
echo "GCD ($a, $b) = $gcd"
break
fi
done
done
You could define a function that implements the Euclidean algorithm:
gcd() (
! (( $1 % $2 )) && echo $2 || gcd $2 $(( $1 % $2 ))
)
the function uses the ternary operator test && cmd1 || cmd2 and recursion (it calls itself). Or define a more readable version of the function:
gcd() (
if (( $1 % $2 == 0)); then
echo $2
else
gcd $2 $(( $1 % $2 ))
fi
)
Test:
$ gcd 88 100
4

Palindrome using shell scripting

I have tried to write a code for checking a string if it is a palidrome but dont know where am I going wrong
read a
len=`echo $a|wc -m`
len=`expr $len - 1`
# echo $len
flag=1
for((i=0;i<len/2;i++))
do
k=`expr $len - $i - 1`
# echo "${a:$i:1} ${a:$k:1}"
if((${a:$i:1} != ${a:$k:1}))
then
flag=0
break
fi
done
if(($flag == 1))
then
echo Palindrome
else
echo Not Palindrome
fi
Your main problem is that you have used ((..)) instead of [[ .. ]] for the string comparison (spacing matters in the latter).
You can get length directly with ${#a}.
Your algorithm starts from both ends and then increments/decrements towards the middle. So you can get flag implicitly - if you pass the midpoint, the string is a palindrome.
You can replace all use of expr with (( .. )).
for ((..)) allows initialising/updating multiple variables.
read a
for (( i=0, k=${#a}-1; i<=k; i++, k-- ))
do
[[ ${a:$i:1} != ${a:$k:1} ]] && break
done
if (( i>k ))
then
echo Palindrome
else
echo Not Palindrome
fi

Print Fibonacci series using recursion in bash with only 1 variable

I'd like to know how to print Fibonacci series using recursion in bash with only 1 variable.
From what I've done:
fib()
{
i=$1
if (( $i <= 1 ))
then echo 0
elif (( $i == 2 ))
then echo 1
else
echo $(( $(fib $(($i - 1)) ) + $(fib $(($i - 2)) ) ))
fi
}
echo $(fib $1)
I get the correct output of the final iteration, for example if I enter 10 I will get 34, but I'd like to print the whole sequence of numbers, i.e. all the iterations as well. How can I achieve that?
Another way I tried was by:
#!/bin/bash
arr[0]=0
arr[1]=1
for (( i=0; i<=10; i++ ))
do
echo -n "${arr[0]} "
arr[0]=$((${arr[0]} + ${arr[1]} ))
arr[1]=$((${arr[0]} - ${arr[1]} ))
done
echo ""
But obviously here I've used a for loop, but I don't want to use another variable.
Just for (my kind of) fun, this code prints the Fibonacci numbers from the 0th to the 92nd (as defined in Fibonacci number - Wikipedia) with a recursive function that uses no variables:
#! /bin/bash
function fib
{
echo ${3-0}
(($1 > 0)) && fib $(($1-1)) ${3-0} $((${2-1}+${3-0}))
}
fib 92
Some may claim that using the positional parameters ($1, $2, $3) for this is cheating, but then other solutions could be said to be using two variables ($i and $1).
The code takes under 0.01 seconds to run on my (oldish) Linux machine.
The code should work with numbers up to 92 with Bash version 3 or later on any platform. See Bash Number Limit?. Numbers higher than 93 will cause to code to produce garbage results due to arithmetic overflow.
Variables in bash are global by default. You need to make i local explicitly.
fib () {
local i
i=$1
if (( i <= 1 )); then
echo $i
else
echo $(( $(fib $((i-1)) ) + $(fib $((i - 2)) ) ))
fi
}
(Also, your base cases are a little off if you are starting with 0, and 2 need not be a base case; fib 2 can be derived from the base cases fib 0 and fib 1.)
If you want to print each fibonacci value from 1 to $n, I suggest:
fib_r() {
local i=$1
if (( i < 0 )); then
echo "Error: negative numbers not allowed" >&2
exit 1
elif (( i <= 1 )); then
echo $i
else
echo $(( $($FUNCNAME $((i - 1)) ) + $($FUNCNAME $((i - 2)) ) ))
fi
}
fib() {
local i
for (( i = 1; i <= $1; i++ )); do
fib_r $i
done
}
fib 10
outputs
0
1
1
2
3
5
8
13
21
34
It's still one variable, albeit one per function.
I use the bash variable $FUNCNAME in the recursive function so you don't have to hardcode the function name within itself. I got bit by not updating that line when I renamed the function.
Of course your performance will greatly improve if you cache the results: "fib 16" takes, on my VM, about 3.5 sec without caching and about 0.03 sec with caching.
fib_r() {
local i=$1
if (( i < 0 )); then
echo "Error: negative numbers not allowed" >&2
exit 1
elif [[ -n ${fib_cache[i]} ]]; then
echo "${fib_cache[i]}"
elif (( i <= 1 )); then
echo $i
else
echo $(( $( $FUNCNAME $((i - 1)) ) + $( $FUNCNAME $((i - 2)) ) ))
fi
}
fib_cache=()
fib() {
local i
for ((i=1; i<=$1; i++)); do
fib_cache[i]=$(fib_r $i)
echo "${fib_cache[i]}"
done
}

Bash script can't figure out how to concatenating white spaces

I want to center a word in X spaces so I wrote this function:
function center {
str=$1
M=$2
N=${#str}
if (( N >= M )); then
echo $str
exit
fi
P=$((M-N))
HP=$((P/2))
left=""
right=""
for i in [1..HP]; do :
left=" $left"
right=" $right"
done
if (( (P-2*HP) == 1 )); then
right=" "$right""
fi
echo "HP is $HP"
echo "Right is |"${right}"|"
echo "Left is |$left|"
echo "str is |$str|"
res=$right$str$left
echo "$res"
}
Problem is not matter what I do can't get right or left to hold on to more than one whitespace. I have tried the suggestions on other answers but I can't seem to make them work. Please help.
Try this (after some variable quoting and some other fixes):
function center {
str=$1
M=$2
N=${#str}
if (( N >= M )); then
echo "$str"
exit
fi
(( P=M-N ))
(( HP=P/2 ))
left=$(printf '%*s' "$HP" "")
right=$left
(( (P-2*HP) == 1 )) && right=" "$right""
echo "HP is $HP"
echo "Right is |${right}|"
echo "Left is |$left|"
echo "str is |$str|"
res="$right$str$left"
echo "$res"
}
The for i in [1..HP] can not work.
A printf '%*s' "$HP" "" is a more idiomatic way to get $HP spaces.
Is better to build one variable $left and copy it to $right than to build the two.

Cannot debug simple ksh programme

I wrote this sample KornShell (ksh) code but it is getting bad substitution error during the if clause.
while ((i < $halflen))
do
if [[${strtochk:i:i}==${strtochk:j:j}]];then
i++
j--
else
ispalindrome = false
fi
done
Please help.
NB: I am using ksh88, not ksh93.
shell syntax is very whitespace sensitive:
[[ is acually the name of a command, it's not just syntax, so there must be a space following it.
The last argument of [[ must be ]], so it needs to be preceded by a space.
[[ works differently depending on the number of arguments it receives, so you want to have spaces around ==
In a variable assignment, you must not have spaces around =.
Tips:
once you figure out it's not a palindrome, break out of the while loop
you are probably checking character by character, so you want ${strtochk:i:1}
i++ and j-- are arithmetic expressions, not commands, so you need the double parentheses.
are you starting with i=0 and j=$((${#strtochk} - 1))?
while ((i < halflen))
do
if [[ ${strtochk:i:1} == ${strtochk:j:1} ]];then
((i++))
((j--))
else
ispalindrome=false
break
fi
done
Check if your system has rev, then you can simply do:
if [[ $strtochk == $( rev <<< "$strtochk" ) ]]; then
echo "'$strtochk' is a palindrome"
fi
function is_palindrome {
typeset strtochk=$1
typeset -i i=1 j=${#strtochk}
typeset -i half=$(( j%2 == 1 ? j/2+1 : j/2 ))
typeset left right
for (( ; i <= half; i++, j-- )); do
left=$( expr substr "$strtochk" $i 1 )
right=$( expr substr "$strtochk" $j 1 )
[[ $left == $right ]] || return 1
done
return 0
}
if is_palindrome "abc d cba"; then
echo is a palindrome
fi
You are using ksh88 but the code you tried is using ksh93 feature missing for the 88 version.
You need to replace
if [[${strtochk:i:i}==${strtochk:j:j}]];then
with these portable lines:
if [ "$(printf "%s" "$strtochk" | cut -c $i)" =
"$(printf "%s" "$strtochk" | cut -c $j)" ]; then
and the incorrect:
i++
j--
with:
i=$((i+1))
j=$((j-1))

Resources