나는 해야 할 프로젝트 목록을 찾았고, 그 중 하나가 엄청난 변화를 가져올 프로젝트였습니다. 나는 다음과 같은 코드를 만들었습니다.
getamt() {
echo "Enter amount of money."
read amount
echo "OK."
}
change() {
amount=$(echo "$amount*100" | bc)
quarter=$(echo "($amount-25)" | bc)
dime=$(echo "($amount-10)" | bc)
nickel=$(echo "($amount-5)" | bc)
penny=$(echo "($amount-1)" | bc )
quarter=${quarter%???}
dime=${dime%???}
nickel=${nickel%???}
penny=${penny%???}
amount=${amount%???}
qNum=0
dNum=0
nNum=0
pNum=0
}
getchange() {
while [ $quarter -ge 0 ]
do
qNum=$(( qNum+1 ))
amount=$(( $amount-25 ))
done
while [ $dime -ge 0 ]
do
dNum=$(( dNum+1 ))
amount=$(( $amount-10 ))
done
while [ $nickel -ge 0 ]
do
nNum=$(( nNum+1 ))
amount=$(( $amount-5 ))
done
while [ $penny -ge 0 ]
do
pNum=$(( nNum+1 ))
amount=$(( $amount-1 ))
done
}
display() {
echo "Your change is:"
echo "$qNum quarters"
echo "$dNum dimes"
echo "$nNum nickels"
echo "$pNum pennies"
}
getamt
change
getchange
display
이것이 내가 해야 할 일을 수행하는 데 좋지 않은 방법일 수도 있다는 것을 알고 있지만 여전히 멈춰 있습니다. 루프를 잘못 사용 하고 있는 것 같지만 while
잘 모르겠습니다. while
루프의 목표는 다른 유형의 동전을 추가할 수 있는지 확인하여 값이 0보다 큰지 확인하는 것입니다 .
답변1
코드의 가장 명백한 문제는 모든 while
루프가 루프 내부에서 절대 변경되지 않는 변수(예: $quarter
)를 확인하므로 루프 조건이 결코 false가 되지 않고 루프가 무한히 반복된다는 것입니다.
다음 루프 중 하나를 살펴보겠습니다.
while [ $quarter -ge 0 ]
do
qNum=$(( qNum+1 ))
amount=$(( $amount-25 ))
done
> 0 이면 $quarter
제어 흐름이 루프로 들어가 $qNum
증가 및 $amount
감소하지만 $quarter
동일하게 유지되므로 또 다른 루프 반복을 수행합니다.
코드 수정은 리팩토링을 통해 가장 잘 작동합니다.
amount
전역 변수가 함수의 부작용으로 설정되는 것에 의존하는 대신 인수를 받아들이고 해당 결과를stdout
(가능한 경우) 출력하도록 함수를 다시 작성하세요.결과
stdout
: 함수는 사용 가능 여부 와 관계없이 나중에 스크립트에서 처리getamt()
될 수 있습니다(변경되지 않음). 무엇을 호출하든 이 출력은 를 사용하여 변수로 캡처할 수 있습니다 . 안타깝게도 함수가 여러 값을 반환해야 하는 경우에는 작동하지 않습니다. 이 경우 함수가 반환 값을 개행 문자로 구분하여 인쇄하도록 할 수 있거나 값에 나타나지 않을 문자를 표시할 수 있습니다. 이와 같은 출력 형식을 선택할 수도 있습니다echo $amount
amount
getamt
amount=$(getamt)
quarter=3 dime=1 nickel=4
그리고 해당 출력을 평가하여 함수의 반환 값을 사용하여 지역 변수를 설정합니다.
$(yourfunction); echo $quarter
매개변수: 함수는 전역 변수에서 읽는 대신
change()
매개변수(즉, 호출함)로 계산해야 하는 변경량을 취할 수 있습니다 . 첫 번째 인수, 두 번째 인수 등amount 2.50
인덱스를 통해 함수(또는 컨텍스트에 따라 스크립트)에 제공된 인수에 액세스할 수 있습니다.$1
$2
bc
소수점 이하 자릿수를 제거하면 여러 번의 호출을 피할 수 있습니다한 번그런 다음 bash 산술 평가를 사용하십시오. 현재 교체 항목${quarter%???}
도 삭제됩니다.어느마지막 세 문자는 사용자가 소수점 두 자리보다 크거나 작은 값을 입력하기로 결정한 경우 원하지 않는 결과를 생성합니다. 비슷한 방법을 사용하여${quarter%%.*}
첫 번째 항목 이후의 모든 항목을 삭제합니다.
.주석 사용(a로 시작하여
#
줄 끝까지 계속):
예를 들어,amount=${amount%%.*} # remove decimal places
대부분의 코드가 지금은 명확해 보일 수 있지만 다른 사람에게는 명확하지 않을 수 있으며, 다음과 같은 경우에도 명확하지 않을 수 있습니다. 몇 달 후에 다시 살펴봐야 하며 더 이상 필요하지 않습니다.솔직히 말해서, 귀하의 스크립트가 현재 반환할 코인 수를 어떻게 계산해야 하는지 완전히 모르겠습니다. 변화량을 계산하는 가장 일반적인 방법은 그리디 알고리즘으로, 사용 가능한 가장 높은 코인 값으로 시작하여 해당 값의 변화량과 "일치하는" 가능한 한 많은 코인을 배포합니다.1, 변경 금액에서 이러한 코인의 총 가치를 뺀 다음 변경 금액이 0에 도달할 때까지 다음(더 작은) 코인 값으로 계속 진행합니다(즉, 전체 변경 금액을 구성하기에 충분한 코인이 배포되었습니다).
1확인할 수 있는 코인 수를 계산하려면모듈로 산술또는 변경 금액이 코인 값보다 작아질 때까지 루프를 실행하여 변경 금액에서 현재 코인 값을 뺍니다(즉, 현재 값의 다른 코인을 나눠주면 너무 많은 잔돈이 반환됩니다).
답변2
다음 두 가지 쉘 함수에서 실제 수학적 계산은 여기에서 수행됩니다.
while set "${1#0?}" "${1#?}"
shift "$((!${#1}))"
[ "${1:-0}" -gt 0 ]
do case $1 in ([3-9]?|2[5-9])
set "$(($1%25))" "$((q+=$1/25))";;
(??) set "$(($1%10))" "$((d=$1/10))" ;;
(?) set "" "$((p=$1-(5*(n=$1>=5))))";;
esac; done
이것이 코인 선택 코드의 전부입니다. 가능한 한 적은 수의 코인을 반환하도록 최적화되어 있습니다. case
쉘 제어문이 작동하는 방식이기 때문에 이를 위해 아무것도 할 필요가 없습니다. 가능한 가장 빠른 일치 항목만 선택하면 됩니다. 따라서 필요한 것은 동전을 가장 큰 것부터 가장 작은 것 순으로 배열하는 것이며, 반복 횟수는 3을 초과하지 않습니다.
위의 유일한 어려운 부분은 08과 09의 경우 결과를 8진수로 잘못 해석하지 않도록 이식 가능한 쉘 수학을 보호하는 것입니다. 이는 루프가 실행될 때마다 앞에 오는 0을 제거하여 처리됩니다.
실제로 아래 기능의 대부분은 입력 유효성 검사 및 오류 보고에 중점을 두고 있습니다. 명시된 목표는 대화형 사용자로부터 입력을 받아 출력을 제공하는 것입니다. 이것은 또한 몇 가지 중요한 사항입니다. 특히 쉘 수학이 관련된 경우에는 더욱 그렇습니다. 쉘 수학은 본질적으로 두 부분으로 구성된 eval
연산이기 때문에 사용자 입력을 산술 명령문에 넣을 때 먼저 그 안에 무엇이 있는지 확인해야 합니다.
case
다시 말하지만, 이것은 이와 같은 작업에 대해 제가 선호하는 형식입니다.
_err()( unset parm msg IFS \
"${1##*[!_[:alnum:]]*}" || exit
parm=$1 IFS=$2 msg=$3; shift 3
eval ': "${'"$parm?\"'\$*' can't be right. \$msg"'"}"'
)
_chg() if set -- "${1#"${1%%[!0]*}"}.${2%"${2#??}"}${3+.}" "$@" &&
case $1 in
(*.*.*) shift
_err too_many_dots . "
We're fresh out of microcoins." "$@" ;;
(-*) shift
_err nice_try_pal . "
Change isn't magic money, you know." "$@" ;;
(*[!0-9.]*) shift
_err i_hate_poetry . "
We only spend numbers around here." "$@" ;;
(.00|.0|.) shift
_err that_was_easy . "
Next time try spending something." 0 00 ;;
esac || return
then set "${1##*.}" "$((q=(${1%%.*}0*4)/10+(d=(n=(p=0)))))"
while set "${1#0?}" "${1#?}"
shift "$((!${#1}))"
[ "${1:-0}" -gt 0 ]
do case $1 in ([3-9]?|2[5-9])
set "$(($1%25))" "$((q+=$1/25))";;
(??) set "$(($1%10))" "$((d=$1/10))" ;;
(?) set "" "$((p=$1-(5*(n=$1>=5))))";;
esac; done
set quarter q dime d nickel n penny p
echo Your change is:
while [ "$#" -gt 1 ]
do printf "\t$1 coins:\t$(($2))\n"
shift 2
done; fi
그러나 실제로는 입력을 받지 않고 read
명령줄 인수로만 입력을 받습니다. 다음을 통해 사용자 입력을 추출할 수 있습니다.
printf '\n$ '; IFS=. read -r dollars cents dot
다음과 같이 직접 전달하십시오.
_chg "$dollars" "$cents" ${dot:+""}
...다른 모든 작업은 자동으로 수행되어야 합니다.
이 _err()
함수는 오류를 보고하고 올바른 반환 값을 얻기 위해 여기나 다른 곳에서 사용할 수 있도록 제가 작성한 재사용 가능한 함수입니다. 확장하면 unset
${var?expansion form}
쉘이 인쇄됩니다확장형stderr로 이동하고 오류 상태로 갑자기 종료됩니다. 이 동작은 일반적으로 직접 처리하려는 테스트 유형에는 잘 작동하지 않지만 해당 unset
매개변수가 완전히 확장되려면 특정 조건이 충족되어야 한다는 것을 알고 있는 경우 이는 확실히 프로세스를 의미하는 조건입니다.~해야 한다그렇다면 이것은 매우 편리한 방법이 될 수 있습니다. 이는 쉘이 자체 표준 방식으로 모든 출력 형식을 지정하기 때문입니다.(대화식 쉘 사용자는 이미 이에 익숙할 수도 있습니다), 종료 코드를 즉시 처리하세요.
예를 들어:
bash -c '. ~/coins.sh
_err parameter_name \
-splitter \
"Some custom message that is also thrown in." \
and my entire input arg array
'
...명령줄에서 실행하면 1을 반환하고 stderr에 인쇄합니다...
/home/mikeserv/coins.sh: line 5: parameter_name: 'and-my-entire-input-arg-array' can't be right. Some custom message that is also thrown in.
따라서 전체 전반부는 _chg()
입력이 올바른지 검증하고, 그렇지 않은 경우 오류 조건 및 오류 출력을 반환하는 데 전념합니다.
모든 것이 순조롭게 진행되면 마지막 분기는 표준 출력 형식화에 전념합니다. 예를 들면 다음과 같습니다.
sh -c '. ~/coins.sh; _chg 10 97'
Your change is:
quarter coins: 43
dime coins: 2
nickel coins: 0
penny coins: 2
답변3
다른 답변은 이미 특정 문제를 해결하고 있습니다. 잠시 노력한 후에 나는 주저했습니다. 따라서 고려할 수 있는 또 다른 접근 방식은 다음과 같습니다. 루프 와 while
다른 . 배열은 코드를 단순화하는 데 도움이 됩니다.until
for
echo "Enter amount of money: $.c or just $"
read amount
echo
a=(${amount/./ }) # change '.' to ' ' and make an array: a[0], a[1]
da=${a[0]} # dollar-amount
pa=$((10#${a[1]})) # penny-amount
cv=(25 10 5 1) # array of coin-values cv[0] ... cv[3] - q d n p
cc=(\ \ \ \ ) # array of coin-counts cc[0] ... cc[3] - q d n p
cn=( quarters dimes nickels pennies ) # array of coin-names
while (( pa > 0 )); do
for (( i=0; i<${#cv[@]}; i++ )); do # process coin-types from hi-val to lo-val
(( (pa-cv[i]) < 0 )) && continue # look-ahead: don't give too much change
(( (pa-=cv[i]) )) # decrement penny-amount
(( cc[i]+=1 )) # increment coin-type counters
done
done
# 'paste' arrrays side by side, and tabulate via 'column'
echo "Your coins change is:" # and show only relevant coins via 'sed'
column -t <(paste <(printf '%s\n' "${cn[@]}") \
<(printf '%s\n' "${cc[@]}")) | sed -n '/[0-9]/p'