Bash 脚本二进制搜索
Posted
技术标签:
【中文标题】Bash 脚本二进制搜索【英文标题】:Bash Script Binary Search 【发布时间】:2013-07-14 00:17:24 【问题描述】:编写一个 bash 脚本来进行二分搜索。将文件中的学生姓名和成绩读入数组。提示用户输入学生姓名。在数组中查找名称并显示成绩。文件中的数据如下:
Ann:A
Bob:C
Cindy:B
Dean:F
Emily:A
Frank:C
Ginger:D
Hal:B
Ivy:A
Justin:F
Karen:D
我已完成以下操作,但我不知道下一步该做什么
#!/bin/bash
echo "please enter students Name: "
read student
echo "$student + $Grade"
((i=0))
while read students[$i] ; do
((i++))
done < students.dat
first=0
last=$(students[@])
((mid=0))
Name=`echo $students[$mid] | cut -d: -f1`
Grade=`echo $students[$mid] | cut -d: -f2`
echo $Name
echo $Grade
【问题讨论】:
你了解二分查找背后的原理吗?例如,你能在纸上一步一步地做吗? 【参考方案1】:二分搜索需要搜索的最大和最小边界。从零开始很好,但你的最后一个变量有点偏离。尝试:last=$(($#students[@] - 1))
- 1 会将您的数组设置为正确的大小(数组从零开始,然后减一。)
然后尝试以下伪代码:
while (last is <= first)
middle = midway point between first and last
// make sure that your comparing just the names "Ann",
// not your whole string "Ann:A"
if (students[middle] == student)
exit loop
else if (students[middle] < student)
first = middle + 1
else if (students[middle] > student)
last = middle - 1
我不擅长 bash 脚本,所以我不会尝试修复(如果它甚至需要修复)你的大部分语法。如果您弄清楚语法,伪代码应该可以帮助您完成大部分工作。
【讨论】:
这太完美了。谢谢【参考方案2】:此解决方案假定您正在寻找第一个成功执行的命令,而不是数组中的元素。
lo=1
hi=100
while [ $(expr $hi - $lo) -ne 1 ]; do
mid=$(expr $lo + '(' $hi - $lo ')' / 2)
# Your command here
test 44 -gt $mid
if [ $? -eq 0 ]; then lo=$mid; else hi=$mid; fi
done
echo "$lo"
这总是打印您的命令执行成功的 first 值,这与 @lovasoa 解决方案不同,该解决方案在大约一半的配置中关闭。您可以使用seq 1 100 | while read o; do SCRIPT; done
来验证这一点,其中SCRIPT
是上述算法,test $o -gt $mid
作为测试命令。
【讨论】:
您的意思只是if [ 44 -gt "$mid" ]
。另见Why is testing "$?" to see if a command succeeded or not, an anti-pattern?
这是故意的,我认为将二进制搜索的程序作为单独的行是这里的优越风格。特别是,如果正在测试的程序是一个带有一堆标志的长命令,那么将其放在 if 之外会使程序和二进制搜索代码之间的分离更加清晰。
听起来你会很高兴发现 shell 函数。如果您认为这很优雅,那么[ $? -eq 0 ] && [ $? -eq 0 ] && [ $? -eq 0 ]
肯定更优雅吗?我的观点是,if
的真正目的是执行命令并检查其退出代码。
我从不写副作用 ifs,我猜我们的编程风格很不一样 :)【参考方案3】:
我认为最好使用通用的二分搜索功能,然后针对您的特定情况编写自己的代码。
Binary search function in bash
# Returns the largest i for which `command i` succeeds (exits with a null exit code)
function dichotomic_search
min=$1
max=$2
command=$3
while [ $min -lt $max ]; do
# Compute the mean between min and max, rounded up to the superior unit
current=`expr '(' "$min" + "$max" + 1 ')' / 2`
if $command $current
then min=$current
else max=`expr $current - 1`
fi
done
echo $min
它使用二进制搜索重复调用作为其最后一个参数给出的函数,以找到它返回 true 的最后一个值。 More explanations on Github
通过 bash 数组进行二进制搜索
在你的情况下,你会这样使用它:
#!/usr/bin/env bash
source dichotomic.sh
arr=(Ann:C Bob:A Cindy:B Dean:E Emily:A Karen:A Zob:A)
function is_smaller
element=$(echo $arr[$2] | cut -f1 -d :)
if [[ "$element" > "$1" ]]
then false
else true
fi
read target
highest_index=`expr $#arr[@] - 1`
index=$(dichotomic_search 0 $highest_index "is_smaller $target")
echo "$arr[$index]"
【讨论】:
【参考方案4】:试试这个,让我得到你的反馈。
#!/bin/bash
##CREATE AN ARRAY VARIABLE TO STORE DATA FOUND IN STUDENT.TXT AT STARTUP
#NAMESARRAY STORE ALL NAMES
declare -a namesarray
#GRADESARRAY STORE ALL GRADES
declare -a gradesarray
#GLOBALMATCHINDEX STORES THE ARRAY INDEX WHERE NAME IS FOUND.... NAMES ARRAY START FROM 0
globalmatchindex=-1
#FUNCTION "CONTAINS" SEARCH THROUGH NAMESARRAY VAIRIABLE TO FIND INPUT FROM USER
function contains()
#CREATE 2 VARIABLES "e" AND "match"
local e match="$1"
shift
#VARIABLE matchindex IS A LOCAL VARIABLE IN THE "CONTAINS" FUNCTION THAT TEMPORARILY STORES THE VALUE OF THE INDEX WHERE INPUTED NAME IS FOUND IN namesarray VARIABLE
local matchindex=0
#LOOP THROUGH namesarray GLOBAL VARIABLE WHICH WAS PASSED AS A PARAMETER TO THE "CONTAINS" FUNCTION
for e;
do
#CHECK IF A MATCHING STRING IS FOUND IN THE namesarray GLOBAL VARIABLE WHICH WAS PASSED AS A PARAMETER
if [ "$e" == "$match" ]; then
#SET THE VALUE OF globalmatchindex GLOBAL VARIABLE TO THE CURRENT LOOP INDEX ALIAS matchindex
globalmatchindex=$matchindex
#EXIT LOOP AND CONTINUE PROCESS
break
fi
#INCREMENT LOCAL matchindex VARIABLE FOR THE NEXT ROUND OF LOOP
matchindex=$((matchindex+1))
done
#FUNCTION "CONTAINS" END HERE
#linenumber GLOBAL VARIABLE STORES THE CURRENT LINE NUMBER IN students.txt FILE
linenumber=0
#A LOOP THAT READ ENTIRE student.txt FILE
while read line; do
#SINCE THE NAMES AND GRADES ARE SEPARATED BY ":" CHARACTER, WE USE A STRING SPLIT METHOD TO SEPARATE NAME FROM GRADE
IFS=':'
#READ EACH LINE AS ARRAY TO "LINEARRAY" VARIABLE. "LINEARRAY" VARIABLE CONTAINS CONTENT LIKE SO "LINEARRAY[0]='JAMES'", "LINEARRAY[1]='A'"
read -ra LINEARRAY <<< "$line"
#STORE THE FIRST STRING IN namesarray GLOBAL VARIABLE
namesarray[$linenumber]=$LINEARRAY[0]
#STORE THE SECOND STRING IN gradesarray GLOBAL VARIABLE
gradesarray[$linenumber]=$LINEARRAY[1]
linenumber=$((linenumber+1))
done < students.txt
while true; do
echo "Enter Student name:"
read studentname
contains "$studentname" "$namesarray[@]"
if [ $globalmatchindex -gt -1 ]; then
echo "Hello $namesarray[$globalmatchindex] your grade is $gradesarray[$globalmatchindex]"
else
echo "Student not found."
fi
globalmatchindex=-1
done
student.txt文件的内容如下。
Ann:A
Bob:C
Cindy:B
Dean:F
Emily:A
Frank:C
Ginger:D
Hal:B
Ivy:A
Justin:F
Karen:D
【讨论】:
近 7 年后你不太可能得到反馈。以上是关于Bash 脚本二进制搜索的主要内容,如果未能解决你的问题,请参考以下文章