⏲️ 공부시간 11 : 20 ~ 12 : 00
오늘은 문자열에 substring 함수를 사용해서 원하는 인덱스의 문자열만 빼내는 문제를 풀었당
문제를 풀면서 어떤 함수가 있는지 하나씩 공부해 볼 수 있어서 좋은 것 같당
오늘 푼 문제/내 풀이/풀이 과정/다른사람풀이가 궁금하다면 링크!
https://velog.io/@orinugoori_art/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-CODEKATA-30-%EA%B0%80%EC%9A%B4%EB%8D%B0-%EA%B8%80%EC%9E%90-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0
⏲️ 공부시간 12 : 00 ~ 14 : 00
에러메세지가 뜰 때 혹은 계산식이 너무 길어지거나 할 때, 텍스트가 밑으로 내려가버림
연산자를 하나밖에 구분못함
1+2-3 하면 0이 되어야하는데 마지막에 입력한 연산자 기준으로 모든 숫자를 처리해버림!
연산자를 연속으로 여러개 입력가능함
+-+- 이렇게도 입력이되어버림.. 연산자를 입력하고 만약에 다른 연산자를 입력하면 그 전에 입력한 연산자는 없어지게 해야할듯..하..
=을 누르면 입력한 식은 사라지고 답만 나오는데 입력했던 식이 위에 작게 표시되어도 괜찮을 것같다.
답으로 너무 너무 큰숫자나오면( Int 범위 벗어난 숫자) 어떻게 처리할지 결정하기
보완 이유 :
텍스트가 2줄로 넘어가게되면 텍스트가 디스플레이 영역을 넘어감
<TextView
android:id="@+id/tv_display"
android:layout_width="0dp"
android:layout_height="0dp"
android:fontFamily="@font/gpz_video_tape"
android:gravity="end|bottom"
android:maxLines="2"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:paddingBottom="5dp"
android:paddingTop="5dp"
android:text="Hello"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="@id/iv_background"
app:layout_constraintEnd_toEndOf="@id/iv_background"
app:layout_constraintStart_toStartOf="@id/iv_background"
app:layout_constraintTop_toTopOf="@id/iv_background"
app:layout_constraintVertical_bias="0.28"
app:layout_constraintHeight_percent="0.11"
app:layout_constraintWidth_percent="0.62">
</TextView>
변경한 것 :
보완 이유 :
연산자를 연속으로 입력할 때 그냥 연산자가 추가되는데 말이 안되는 식이기 때문에 제한하려고함!
val numberButtonList = listOf(
binding.ibtnSeven,
binding.ibtnEight,
binding.ibtnNine,
binding.ibtnFour,
binding.ibtnFive,
binding.ibtnSix,
binding.ibtnOne,
binding.ibtnTwo,
binding.ibtnThree,
binding.ibtnZero,
)
val operatorButtonList = listOf(
binding.ibtnDot,
binding.ibtnPlus,
binding.ibtnMinus,
binding.ibtnMultiple,
binding.ibtnDivide,
binding.ibtnMod
)
for (button in numberButtonList) {
button.setOnClickListener {
addCurrentInput(button.contentDescription.toString())
}
}
for (button in operatorButtonList){
button.setOnClickListener {
addOperatorInput(button.contentDescription.toString())
}
}
//중간 생략//
private fun addCurrentInput(letter: String) {
currentInput += "$letter"
displayText.text = currentInput
}
private fun addOperatorInput(unduplicableLetter: String){
if( currentInput.isNotEmpty() && "+-*/%.".contains(currentInput.last())){
currentInput = currentInput.dropLast(1) + unduplicableLetter
}else{
currentInput += unduplicableLetter
}
displayText.text = currentInput
}
변경된 점 :
addOperatorInput 함수를 생성addOperatorInput 은 현재 입력된 텍스트가 있고, 그 중에 연산자와 . 을 마지막에 가지고 있을때는 마지막 글자하나를 지우고 현재 입력한 글자를 입력하게 하고, 그렇지 않으면 그냥 입력한 글자를 입력하게함으로써 중복입력을 제한함보완 이유 :
1+2-1 이런식으로 식에 연산자가 여러개가 들어가면 답이 2가 되어야하는데
마지막으로 들어간 연산자인 -만 인식하고 1-2-1 로 계산하고 -2를 뱉어버림 도라인가
예쁘기만하고 반쯤 바보인 계산기를 만드는것도 신박하긴 할 수도있겠지만 일단은 고쳐보려고함..
계산기가 멍청한 이유 :
for (i in operators.indices) {
result = when(operators[i]){
"+" -> calculator.add(numbers)
"-" -> calculator.subtract(numbers)
"*" -> calculator.multiply(numbers)
"/" -> calculator.divide(numbers)
"%" -> calculator.mod(numbers)
else -> {
displayText.text = "Error"
return
}
}
}
여기 코드가 연산자를 구분해서 어떤 함수를 실행할지 결정하는 부분인데
반복하는 과정에서 numbers (입력받은 식에서 숫자만 저장해논 리스트) 전체를 넣고 계산하고,
다음 연산자를 가면 또 그 연산자에 전체를 넣고 계산해서 넣고 이런식이라 마지막 연산자로만 계산되는 거였음! 내가 널 바보로 만들었구낭~! ㅎㅣ 히 내가 바보는 나야 계산기가 아니라 ~
수정된 코드
Calculator.kt
class Calculator {
fun add(numbers: List<Number>): Number{
return if (numbers.isEmpty()) {
0
} else {
val sum = numbers.sumOf { it.toDouble() }
if (sum % 1.0 == 0.0) {
sum.toInt()
} else {
sum
}
}
}
fun subtract(numbers: List<Number>): Number {
var calculateNumber = numbers[0].toDouble()
for (i in 1 until numbers.size) {
calculateNumber -= numbers[i].toDouble()
}
return if (calculateNumber % 1.0 == 0.0) {
calculateNumber.toInt()
} else {
calculateNumber
}
}
fun multiply(numbers: List<Number>): Number {
var calculateNumber = numbers[0].toDouble()
for (i in 1 until numbers.size) {
calculateNumber *= numbers[i].toDouble()
}
return if (calculateNumber % 1.0 == 0.0) {
calculateNumber.toInt()
} else {
calculateNumber
}
}
fun divide(numbers: List<Number>): Number {
var quotient = numbers[0].toDouble()
for (i in 1 until numbers.size) {
quotient /= numbers[i].toDouble()
}
return if (quotient % 1.0 == 0.0) {
quotient.toInt()
} else {
quotient
}
}
fun mod(numbers: List<Number>): Number {
var calculateNumber = numbers[0].toDouble()
for (i in 1 until numbers.size) {
calculateNumber %= numbers[i].toDouble()
}
return if (calculateNumber % 1.0 == 0.0) {
calculateNumber.toInt()
} else {
calculateNumber
}
}
}
변경된 부분 :
저렇게 수정한 이유 :
MainActivity.kt calculateResult 함수부분
private fun calculateResult() {
val numbers = stringAnalyze.filterNumbers(currentInput)
val operators = stringAnalyze.filterOperators(currentInput)
if (numbers.isEmpty() || operators.isEmpty()){
displayText.text = "there's no number"
return
}
var calculateResult = listOf(numbers[0])
for (i in operators.indices) {
val nextNumber = numbers[i+1]
val currentNumbers = listOf(calculateResult.last(), nextNumber)
calculateResult = listOf(
when (operators[i]) {
"+" -> calculator.add(currentNumbers)
"-" -> calculator.subtract(currentNumbers)
"*" -> calculator.multiply(currentNumbers)
"/" -> calculator.divide(currentNumbers)
"%" -> calculator.mod(currentNumbers)
else -> {
displayText.text = "Error"
return
}
}
)
}
val result = calculateResult.toString().removeSurrounding("[","]")
displayText.text = result
currentInput = result
}
}
변경된 부분 :
currentNumbers 와 nextNumber 변수를 만들어서 연산함수에 게산하는 순서에 맞는 숫자가 들어갈 수 있게 변경했다.보완 이유 :
보완 방향 :
근데 이거 하려면 지금 당장은 Display 크기가 작아서 디스플레이를 다시 그리던가, 계산식만 표시하는 디스플레이 하나를 추가하던가 해야할 것 같음..
디자인을 수정해야된다는건데 일단은 과제로 하는 거기 때문에 과제를 다 하고나면 그 때 수정해야 할 것 같다.
보완 이유 :
지금 Int와 Double 형의 타입을 사용하고 있는데, 계산기에 Int범위가 넘는숫자를 입력해도 제한하지 않아서 답의 크기가 Int 형 보다 커지면 그냥 Int의 최대값을 표시하기 때문에 조치가 필요함!
보완 방향 :
이것도 근데 당장 하지않고 일단 구현할 기능을 다 구현하고 수정해야겠다.
⏲️ 공부시간 14 : 00 ~ 15 : 30
Lv3
AddOperation(더하기), SubstractOperation(빼기), MultiplyOperation(곱하기), DivideOperation(나누기) 연산 클래스를을 만든 후 클래스간의 관계를 고려하여
Calculator 클래스와 관계를 맺기
- 관계를 맺은 후 필요하다면 Calculator 클래스의 내부 코드를 변경하기
- 나머지 연산자(%) 기능은 제외합니다.
- Lv2 와 비교하여 어떠한 점이 개선 되었는지 스스로 생각해 봅니다.
- hint. 클래스의 책임(단일책임원칙)
Lv3 에서 해야 할 일 정리
일단 기능적으로 변하는건 없고 클래스를 더 세분화해서 하나의 클래스가 하나의 기능을 하도록 정리해주는 단계인 것 같다.
Operation Interface 생성
interface Operation {
fun apply(numbers:List<Number>):Number
}
Calculator의 클래스에 들어있는 연산 함수들을 분리 시킬건데
연산 함수들은 모두 Number 형태의 리스트를 받아서 Number를 반환하기 때문에 이렇게 인터페이스를 만들었다.
각 연산 별로 클래스 생성
class AddOperation : Operation {
override fun apply(numbers: List<Number>): Number{
return if (numbers.isEmpty()) {
0
} else {
val sum = numbers.sumOf { it.toDouble() }
if (sum % 1.0 == 0.0) {
sum.toInt()
} else {
sum
}
}
}
}
각 연산하는 함수를 새로운 클래스로 각각 생성해서
Operation 인터페이스를 상속? 받아서 apply라는 이름의 함수로 다시 구현해줬다.
Calculator 클래스 수정
open class Calculator {
private val operations = mapOf<String, Operation>(
"+" to AddOperation(),
"-" to SubstractOperation(),
"*" to MultiplyOperation(),
"/" to DivideOperation(),
"%" to ModOperation()
)
fun calculate(numbers: List<Number>, operator : String): Number {
val operation = operations[operator] ?: throw IllegalArgumentException ("invalid operator")
return operation.apply(numbers)
}
}
원래 있던 함수들이 다 빠지고 빈 Calculator 클래스에
operations 라는 변수를 만들어 각 연산자와 매칭되는 연산클래스들을 맵핑해주고,operations 에 맵핑되어있는 대로 연산을 실행하게 만드는calculate 함수를 만들었다.MainActivity.kt 의 calculateResult() 수정
private fun calculateResult() {
val numbers = stringAnalyze.filterNumbers(currentInput)
val operators = stringAnalyze.filterOperators(currentInput)
if (numbers.isEmpty() || operators.isEmpty()){
displayText.text = "there's no number"
return
}
var calculateResult: Number = numbers[0]
for (i in operators.indices) {
val nextNumber = numbers[i+1]
val currentNumbers = listOf(calculateResult, nextNumber)
calculateResult = calculator.calculate(currentNumbers, operators[i])
}
val result = calculateResult.toString().removeSurrounding("[","]")
displayText.text = result
currentInput = result
}
원래 for문 안에서 when으로 연산자를 구분해서 Calculator의 함수를 실행했는데, 그 기능을 Calculator에 넣었기 때문에 그냥 calculator.calculate에 계산할 숫자와, 연산자를 넣어준느 걸로 간단하게 바뀌었다.
일단 나도 원래의 코드를 만들면서, MainActivity에서
연산자를 구분하는 기능을 넣는게 너무 지저분하고 기능이 분류가 제대로 안되는 것 같아서 불편했는데 어떻게 해결해야할지 골치가 아파서 일단은 자세만 고쳐앉았었는데,
Calculator 에 있던 연산 함수들이 각자의 클래스로 빠지고 그 과정에서 Interface를 사용해서 각각의 연산 클래스를 같은 인터페이스로 구현하니까, 빈 Calculator 클래스에 연산자를 구분해서 계산을 진행하는 코드를 넣으면 딱 맞겠다고 생각했다.
각 연산이 클래스로 따로 파일이 있어서 찾기도 수월한 것 같고,
전 코드에 비해서 코드가 보기 쉬워진 것 같다.
⏲️ 공부시간 15 : 30 ~ 16 : 20
Lv4Lv 4 는 AbstractOperation이라는 추상 클래스를 만들어서 추상화 하면 될듯??
추상 클래스 생성
abstract class AbstractOperation {
protected fun analyzeResult(result : Number) :Number {
return if (result.toDouble() % 1.0 == 0.0 ) result.toInt() else result
}
abstract fun apply (numbers : List<Number>) :Number
}
analyzeResult 함수로 만들어서 추상클래스에 넣어줌apply 함수를 추상클래스에 넣어줌연산클래스 수정
class AddOperation : AbstractOperation() {
override fun apply(numbers: List<Number>): Number{
val calculatorNumber = numbers.sumOf { it.toDouble() }
return analyzeResult(calculatorNumber)
}
}
Calculator 클래스 수정
open class Calculator {
private val operations = mapOf<String, AbstractOperation>(
"+" to AddOperation(),
"-" to SubstractOperation(),
"*" to MultiplyOperation(),
"/" to DivideOperation(),
"%" to ModOperation()
)
fun calculate(numbers: List<Number>, operator : String): Number {
val operation = operations[operator] ?: throw IllegalArgumentException ("invalid operator")
return operation.apply(numbers)
}
}
연산자와 연산클래스를 맵핑하는 과정에 타입을 추상클래스 이름으로 변경해줌
생성했던 Operation Interface를 삭제해줌
인터페이스를 활용해서 추상화 했을 때는, Interface 안에는 따로 함수 내용을 넣지 않는게 권장되기 때문에 결과값이 정수인지 실수 인지 판단을 각 연산클래스에서 진행했는데,
추상클래스에는 공통되는 함수를 같이 넣어놔도 되는 점을 이용해서
analyzeResult 라는 함수를 만들어서 정수와 실수를 판단해서 Int와 Double 로 결과값을 구분해주는걸 따로 뺐다.
이렇게 하니까 각 연산 클래스의 코드가 간단해지고, 딱 연산만 담당할 수 있게 된 것 같아서 속이 시원해짐.
⏲️ 공부시간 17 : 00 ~19 : 20
보완 이유 :
레이아웃 배치할 때 절대적인 크기로 지정해놓았더니 화면크기가 바뀌면 완전 이상해짐
display 이미지
<ImageView
android:id="@+id/iv_display"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="fitXY"
android:src="@drawable/iv_display"
app:layout_constraintBottom_toBottomOf="@+id/iv_background"
app:layout_constraintEnd_toEndOf="@+id/iv_background"
app:layout_constraintHeight_percent="0.2"
app:layout_constraintStart_toStartOf="@+id/iv_background"
app:layout_constraintTop_toTopOf="@id/iv_background"
app:layout_constraintVertical_bias="0.3"
app:layout_constraintWidth_percent="0.8">
</ImageView>
display text의 크기와 위치도 같은 방식으로 상대적으로 바뀌도록 설정해줬다.
근데 버튼 크기랑 배열은 손을 못대겠다…
이게 다 png 파일 넣었을 때 딱 이미지 크기대로가 아니라 대지 영역이 같이 인식돼서 그럼..슈발
맞추기 너무 힘들다.
아무리 찾아봐도.. 빈 공간영역을 같이 안잡는 방법이 안나옴
내가 절대적인 크기를 지정해주면 가능한데, 그러면 화면크기 바뀌는거에 대응이 안되잖아요;;
ㅜㅜㅜ 애초에 저장할 때 잘 저장해야하는건지..??아니면 png 말고 svg로 저장하면 ㄱㅊ은가??
일단 포기…
가로로 된 화면에는 어떻게 하는지도 찾아봤는데,
가로용 액티비티를 따로 작성해주는것같다.
그래서 일단은 세로용 액티비티를 잘 만질 수있으면 그다음에 가로화면을 하던가해야할듯.
지금 당장 세로용도 버튼배열 못해서 난리남..
보완 이유 :
UI 디자인 한거는 살짝 레트로한 느낌의 계산기인데 소리가 안어울림!
더 어울리는 소리로 바꾸고싶당
효과음 변경한 방법
<ImageButton
android:id="@+id/ibtn_parenthess"
android:layout_width="70dp"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:background="@android:color/transparent"
android:src="@drawable/pressedmotion_parentheses"
app:layout_constraintStart_toEndOf="@+id/ibtn_mod"
app:layout_constraintTop_toTopOf="@id/ibtn_mod"
app:layout_constraintBottom_toBottomOf="@+id/ibtn_mod"
android:layout_marginStart="5dp"
android:contentDescription="()"
android:soundEffectsEnabled="false"
/>
soundEffectsEnabled 를 false로 설정하면 기본 효과음이 빠진다.
res 폴더에 raw 폴더 만들어주고 원하는 소스 추가
MainActivity에 코드 추가
private lateinit var mediaPlayer: MediaPlayer
mediaPlayer = MediaPlayer.create(this,R.raw.vintage)
for (button in numberButtonList) {
button.setOnClickListener {
playSound()
addCurrentInput(button.contentDescription.toString())
}
}
private fun playSound(){
if(mediaPlayer.isPlaying){
mediaPlayer.stop()
mediaPlayer.release()
mediaPlayer = MediaPlayer.create(this, R.raw.vintage)
}
mediaPlayer.start()
}
override fun onDestroy(){
super.onDestroy()
mediaPlayer.release()
}
}
mediaplayer 라는 변수를 만들고, 거기에 MediaPlayer라는 애를 가져와서 넣고
playSound() 함수를 만들어줌 근데 나 이거는 그냥 따라한거라 쟤네가 정확히 무슨역할하는지는 안찾아봄..나중에 더 공부할랭
그리고 setOnClickListenr 에 playSound함수를 넣어서 버튼 누르면 소리나게 해줌!
근데 지금 소리 대충 찾아서 넣었더니 이것도 다시 찾아봐야할 것 같다.
잘 안어울리는 거 같음 ;