0

I am trying to use binary search to display a grade of a student if they are in the array. There is no issue with my search, however, I am apparently not correctly comparing the data in my conditional statements. The elif runs no matter what name I enter. There seems to be some problem with at least one of the values that I am comparing. Here is what is stored in the array:

Ann:A
Bob:C
Cindy:B
Dean:F
Emily:A
Frank:C
Ginger:D
Hal:B
Ivy:A
Justin:F
Karen:D 

Here is my script.

#!/bin/bash

# Create array from student list
studentArray=($(cat ./studentList.dat))

# Ask for student name
echo "Enter Student Name"
studentName=$(read)

# variable to store if a student has been found 
studentFound="false"
# variable to store student grade
studentGrade=""

# start and end index of the studentArray
START=0
END=$((${#studentArray[@]}-1))


# binary search
while [ "$START" -le "$END" ]; do
 MIDDLE=$((START+((END-START)/2)))
 middleItem=$(echo ${studentArray[$MIDDLE]} | cut -d: -f1)

 if [ "$studentName" = "$middleItem" ]; then
  studentFound="true"
  studentGrade=$(echo ${studentArray[$MIDDLE]} | cut -d: -f2)
  break
 elif [[ "$studentName" < "$middleItem" ]]; then
  ((END=MIDDLE-1))
 else
  ((START=MIDDLE+1)) 
 fi
done

if [ "$studentFound" == "true" ]; then
 echo $studentGrade
else
 echo "Sorry, there is no student by that name"
fi

2 Answers2

2

The very short version of the fix: studentName=$(read) doesn't work, use read -r studentName instead.

The longer fix, including a few pointers:

  • read a file into an array using

    mapfile -t studentArray < studentList.dat
    

    This avoids surprises from the unquoted expansion you might otherwise see.

  • echo and read can be combined in Bash:

    read -rp "Enter Student Name: " studentName
    

    The -r is to avoid backslash interpretation, and -p lets you specify a prompt.

  • Avoid all uppercase variable names: they're more likely to clash with shell and environment variables. Also, Bash has a conditional construct that provides an arithmetic context:

    while ((start <= end)); do
        middle=$((start + (end-start)/2))
        ...
    
  • To get the first part of a colon separated string, you can use parameter expansion:

    middleItem=${studentArray[middle]%:*}
    

    Notice that the index doesn't require an $ because it's an arithmetic context.

  • The same goes for the second part of the colon separated string:

    studentGrade=${studentArray[middle]#*:}
    

Taken all together:

#!/usr/bin/env bash

# Create array from student list
mapfile -t studentArray < studentList.dat

# Ask for student name
read -rp "Enter Student Name: " studentName

# Variable to store if a student has been found
studentFound="false"

# Start and end index of the studentArray
start=0
end=$((${#studentArray[@]} - 1))

# Binary search
while ((start <= end)); do
    middle=$((start + ((end - start) / 2)))
    middleItem=${studentArray[middle]%:*}

    if [[ $studentName == "$middleItem" ]]; then
        studentFound="true"
        studentGrade=${studentArray[middle]#*:}
        break
    elif [[ $studentName < $middleItem ]]; then
        ((end = middle - 1))
    else
        ((start = middle + 1))
    fi
done

if [[ $studentFound == "true" ]]; then
    echo "$studentGrade"
else
    echo "Sorry, there is no student by that name"
fi
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
1

The code uses read incorrectly. The read function read a line into variable(s), and does not print the input line. Therefore studentName=$(read) set studentName to ''

Proposed Fix:

read studentName

Side note: One of the simplest thing to do with a script is to add 'set -x' to the top, or invoke the script with bash -vx script .... This will provide line by line information (including variable assignment), which make it is easy to identify the problem. For above script, the output will be:

+ studentArray=($(cat ./studentList.dat))
++ cat ./studentList.dat
+ echo 'Enter Student Name'
Enter Student Name
++ read
Hal
    <-- Error here -->
+ studentName=
+ studentFound=false
+ studentGrade=

Showing studentName was not set properly.

dash-o
  • 13,723
  • 1
  • 10
  • 37